I was looking at the various mobile app stores and their ability to push apps out to countries all over the world and I thought that I needed to write an app that took advantage of this worldwide distribution. My reasons for doing so were two-fold: first, it was an exercise in learning, which I’m always up for; second, I thought it may actually help increase sales in foreign countries (or at least couldn’t hurt).
The goal as I saw it was to have an app that was completely usable in multiple languages. There would be nothing that wasn’t translated. Also, the app would need to respect the language setting of the phone and, if possible, translate the app into the user’s desired language.
The first place that I started was in deciding which languages to choose. I ruled out Mandarin Chinese, Hindi, and all other languages that need a non-Latin alphabet and/or read right to left. If you’re going to translate your app into those types of languages there is much more work to do. I then chose languages that were either in the top 10 of the world, or they were languages that I had some fluency with. In the end I chose four: English, French, Spanish and German.
Once I chose the languages, I went about finding a font that worked for my app and would also support all of the “special” characters that would be needed, like e with an acute accent: “é”. I settled on Architect’s Daughter, but there are thousands that will work just fine. Pick one that works for your app. Here is what it looks like in the Font Book application on a Mac – notice all the nicely accented characters:
For the app to be able to translate text from the base language to a second language, some translation table must be built. You can translate words or phrases. I decided to store these translations in a SQLite database in the app, but you could use text files or simply assign tables in Lua directly. The database creation SQL is this:
local sqlite3 = require("sqlite3"); local dbPath = system.pathForFile("gamedata.db3", system.DocumentsDirectory); local db = sqlite3.open( dbPath ); local tablesetup = [[ CREATE TABLE IF NOT EXISTS languages ( lang_code TEXT, language TEXT ); CREATE TABLE IF NOT EXISTS translate ( lang_code TEXT, key TEXT, result TEXT ); ]] db:exec( tablesetup );
Once the database tables are created, they must be populated. First, all of the languages that the app will support:
datasetup = [[ INSERT INTO languages VALUES ('en','English'); INSERT INTO languages VALUES ('fr','Français'); INSERT INTO languages VALUES ('es','Español'); INSERT INTO languages VALUES ('de','Deutsch'); ]]; db:exec( datasetup );
And then the translations. Here are a few from the app I built:
datasetup = [[ INSERT INTO translate VALUES ('en','menu','Menu'); INSERT INTO translate VALUES ('en','play','Play'); INSERT INTO translate VALUES ('fr','menu','Menu'); INSERT INTO translate VALUES ('fr','play','Jouez'); INSERT INTO translate VALUES ('es','menu','Menú'); INSERT INTO translate VALUES ('es','play','Jugar'); INSERT INTO translate VALUES ('de','menu','Menü'); INSERT INTO translate VALUES ('de','play','Spielen'); ]]; db:exec( datasetup );
All of the above is run only once on database creation.
To store the target language in the app and have it usable from within any module, I use a global variable called “settings”, and one of those settings is the language:
_G.settings = {} settings["language"] = "en";
With this default value the app will always fall back to English if we can’t determine the language at run time, or if the phone language isn’t one that is supported by the app. To determine the language that the user has set their phone to, we use this code:
-- set the language from the operating system if possible local lang if system.getInfo( "platformName" ) ~= "Android" then lang = system.getPreference( "ui", "language" ):lower() else lang = system.getPreference( "locale", "language" ):lower() end
And then this code to actually switch the language (notice that I have restricted it to only the four languages that the app supports):
if lang == "es" or lang == "fr" or lang == "de" or lang == "en" then settings["language"] = lang end
Now that the database is full of languages and translations, and we know the desired language to display, we need a way to translate. For text translations, a utility module is created that will perform all the translations. Every time we need to display some text in the app, we give the word or the phrase key to the translation module, and it returns the translated text.
local M = {} -- Translates a word or key into a specific language local function find(key) local lookupKey = string.lower(key) local lookupResult local dbPath = system.pathForFile("gamedata.db3", system.DocumentsDirectory) local db = sqlite3.open( dbPath ); for row in db:nrows("SELECT result FROM translate WHERE lang_code = '"..settings["language"].."' and key = '"..lookupKey.."'") do lookupResult = row.result end if lookupResult == nil then -- When debugging use this line --lookupResult = '??' -- For release, use this line lookupResult = key end db:close() return lookupResult end M.find = find return M
The translation module is assigned and called like this:
local T = require("translator")
And then everywhere in the app where some text is to be translated and displayed, the translation module is called like this:
playGameText = display.newText(T.find("Play"), 0,0, settings["font1"], 15)
For image translation, you will need to create one version of the image for every language that you want to support. Here is my title image in English:
The code to create the proper image at run time is similar to this:
titleImage = display.newImageRect("images/menu/"..settings["language"].."/title.png", 320, 120)
You could also save the images all in the same directory and have language codes in the file names, but I chose to name the images all the same name but in separate directories, like this:
Finally, I chose to allow the user to change the app language at run time if they wish, but since we’re honoring the phone language setting that probably isn’t needed. It’s just fun for me to show people the translations in real time without having to adjust my phone’s language setting.
The last important part about creating an app in multiple languages comes when publishing to the Apple app store. In the build.settings file, you need to specify which languages are supported. This will allow end users to see that the app supports their language when they are browsing and see your app in the app store:
iphone = { plist = { CFBundleLocalizations = { "English", "French", "German", "Spanish" }, } },
I’ve put together a demo app that you can download and view in the Corona simulator: MultiLangDemo.zip
All of these concepts were used in creating Wabbit Wars and Spiky Swim. Check them out if you’d like to see this working on a published app.
If you found this tutorial useful, please comment and let me know! If you’ve read this far, you may want to find me on Mastodon: https://mastodon.gamedev.place/@toddtrann