Cocos2D-x has some support for multiple languages, but unfortunately I am not really convinced by it. So I figured why not use a proven, semi-standard technology like gettext to handle this? Gettext works by parsing .mo files which are compiled language files. They are compiled from what are called .po files which can be edited with a text editor, an online editor or a specialized tool like the most excellent free Poedit. If you format correctly all your strings that need translation, Poedit will extract them automatically.
The way to do this is to reference them like this:
Instead of label->setString("Start")
you will use label->setString(_("Start").c_str());
The reason you’re using .c_str()
on the returned string is that the _()
function returns a std::string
, while Cocos2D-x labels accept const char *
as parameters. If you really want, you can declare a __()
macro that would convert __(str)
to _(str).c_str()
, but that is left as an exercise for the reader.
Once you have wrapped all your strings in the _()
function call, create a new translation project in Poedit, select the desired language (for example French) and save the file into MyGame/Translations (you’ll need to create your Translations folder). A good idea for the file name is [language code].po, in this case fr.po. When you press the “Update” button in Poedit, it will go over your .cpp files and save any strings it finds. You can then save the translation (CMD + s)
which will generate the .mo file.
Each translation will have its own .po and .mo files, so if your game has support for German and French apart from English (which I assume you use for your default strings) you will end up with two pairs of localization files, de.po/de.mo and fr.po/fr.mo.
Implementation
Gettext is not readily available on certain platforms, but it needs not be, actually. We can use a C++ library called moFileLib. Now, unfortunately, due to an implementation detail, you can’t access the translation files from the Android build as easily as you can from the iOS build and moFileReader only supports language loading based on a supplied filename. To work around this, I added a ParseData() method that expects the already loaded file instead of the filename. This updated version of moFileLib can be found here.
After you download it, copy the moFileConfig.h
, moFileReader.h
and moFileReader.cpp
files into your project. Make sure you update your XCode project and build_native.sh as well.
The best spot to initialize the language is in AppDelegate.cpp. Add the following declaration in AppDelegate.h:
void loadLanguageFile();
In the AppDelegate.cpp file add the following:
#include "moFileReader.h" using namespace moFileLib;
To load our language file we’ll need to get the current language, get the file, parse it and send it to moFileReaderSingleton
:
void AppDelegate::loadLanguageFile() { moFileReader::eErrorCode error = moFileLib::moFileReader::EC_SUCCESS; moFileReaderSingleton::GetInstance().ClearTable(); unsigned char *charData; unsigned long nSize; switch (CCApplication::sharedApplication()->getCurrentLanguage()) { case kLanguageFrench: { charData = CCFileUtils::sharedFileUtils()->getFileData("fr.mo", "r", &nSize); std::string data(reinterpret_cast<const char *>(charData), nSize); error = moFileReaderSingleton::GetInstance().ParseData(data); break; } default: break; } if (error) { CCLOG("Read localization file for language %d failed with error %d", CCApplication::sharedApplication()->getCurrentLanguage(), error); } }
The next step would be to tell Cocos2D-x where we are storing our translation files. If you are doing the runtime resolution check and set the searchResolutionOrder
in applicationDidFinishLaunching
in the application delegate file, include the following line before calling setSearchResolutionsOrder
:
resDirOrders.push_back("Translations");
Another option would be to call:
std::vector<std::string> searchPaths = CCFileUtils::sharedFileUtils()->getSearchPaths(); searchPaths.insert(searchPaths.begin(), "Translations"); CCFileUtils::sharedFileUtils()->setSearchPaths(searchPaths);
Once this is done, the framework will be able to find your translations and load them up after you add the following line at the end of the
Actually translating the strings
After you have completed your game and your strings are ready for translation, just open the .po files in Poedit, hit Update and then translate the strings into the new languages or send the .po files to the translators. Most translation software has support for .po files. Alternatively, you can even set up a simple translation interface on your own webserver, such as simplepo, then send the translator the link and let them do their own thing.
Once you have the translations, you have to include them in your project. Add the “Translations” folder as a folder reference to your XCode project and then edit build_native.sh and add the following before the # run ndk-build
line:
# copy translations for file in "$APP_ROOT"/Translations/* do if [ -f "$file" ]; then cp "$file" "$APP_ANDROID_ROOT"/assets fi done
Using a lanuage not included in Cocos2D-x
If you need a language not defined in the LanguageType
enum, you’ll have to edit the CCCommon.h file and add it at the end of the enum definition. Then, edit CCApplication.mm and add it at the end of the getCurrentLanguage()
method. And you’re still not done yet, because you’ll also have to add it in CCApplication.cpp in cocos2dx/platform/android.