[tutorial] Multiple language support

@agenCi How about you try to have a default language and a current language, both loaded in memory, so when “getStringForKey” doesn’t find a string, it gets it from the default language (which must have the string), otherwise you do what you need to do.

Regardless though, you should always handle errors gracefully, and a crash isn’t very graceful. :wink:

I’ll show [key], if no value is found. This way I can see easier missing entries. The advice from @R101 will make it harder to fix missing translations.

1 Like

@mars3142 how does your code looks like to use your {key}-value?

Hi, I’m testing the code that posted by @starfair
And my game is entering in the if statement document.HasParseError

    //Read and parse language data file with rapidjson library
    string clearContent = FileUtils::getInstance()->getStringFromFile(fileName);
    
    document.Parse<0>(clearContent.c_str());
    if(document.HasParseError())
    {
        CCLOG("%u", document.GetParseError());
        CCLOG("Language file parsing error!");
        return;
    }

The Parse Error code is 4.
This is the content of es.json:

{
    "main_menu_play_button_label":"Play",
    "mode_selection_menu_label":"Select mode",
    “PAUSE_LAYER_TITLE”:”GAME PAUSED”
}

What could be my problem?

I found the problem. The file was being saving wrong and some characters were not identified.

[SOLVED] First of all, thanks for this piece of code, it’s been very helpful… I have the following code on cocos 3.16…

LanguageManager* LanguageManager::_instance = 0;
using namespace rapidjson;

LanguageManager::LanguageManager() {
    string fileName;
    // detect current language
    switch(Application::getInstance()->getCurrentLanguage())
    {
        case LanguageType::ENGLISH:
            fileName = "en.json";
            break;
        case LanguageType::SPANISH:
            fileName = "es.json";
            break;
        default:
            CCLOG("Unknown language. Use english");
            fileName = "en.json";
            break;
    };

    // below we open, read and parse language data file with rapidjson library
    const string buf = FileUtils::getInstance()->getStringFromFile(fileName.c_str());
    string clearContent = buf.substr(0, buf.rfind('}') + 1);

    document.Parse<0>(clearContent.c_str());
    if(document.HasParseError())
    {
        CCLOG("Language file parsing error!");
        return;
    }
}

LanguageManager* LanguageManager::getInstance() {
    if(!_instance)
        _instance = new LanguageManager();
    return _instance;
}

string LanguageManager::getStringForKey(string key) {
    return document[key.c_str()].GetString();
}

std::vector<std::string> LanguageManager::getArrayForKey(string key) {
    std::vector<std::string> s;
    const rapidjson::Value& v = document[key.c_str()];
    for (rapidjson::SizeType i = 0; i < v.Size(); i++) {
        s.push_back(v[i].GetString());
    }
    return s;
}

And I’m getting a crash on some iPhones… here it’s the trace from crashlytics:

Crashed: com.apple.main-thread
0  libsystem_platform.dylib       0x1c70fb3a4 _platform_strlen + 4
1                            0x10510fb24 std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string<std::nullptr_t>(char const*) + 1762 (string:1762)

This happens when calling getStringForKey, but only happens on a bunch of devices, most of the time works fine, so I’m sure the key and string exist… Last, the string is 101 char long.

Any ideas? I don’t have a device to reproduce and do proper debugging so that’s all the information I have…

Could it be possible that the json file is not properly loaded for some reason?

Many thanks in advance.

If I am reading that crash correctly, it looks like you’re trying to either assign a nullptr to a std::string, or you’re trying to de-reference a nullptr into a std::string. You should really be validating any data, because you’re making a lot of assumptions about data that is being read from a file.

For instance:

string LanguageManager::getStringForKey(string key) {
    if (!document.HasMember(key.c_str()) || document[key.c_str()].IsNull())
        return ""; 

    return document[key.c_str()].GetString();
}

std::vector<std::string> LanguageManager::getArrayForKey(string key) {
    if (!document.HasMember(key.c_str()) || document[key.c_str()].IsNull() || !document[key.c_str()].IsArray())
        return std::vector<std::string>(); 

    std::vector<std::string> s;
    const rapidjson::Value& v = document[key.c_str()];
    for (rapidjson::SizeType i = 0; i < v.Size(); i++) {
        s.push_back(v[i].GetString());
    }
    return s;
}

There could be more code there that isn’t quite protected, but it’s up to you to go through it and make sure you cover all possible points of failure.

Aside from that, if you define RAPIDJSON_HAS_STDSTRING=1 in your makefile (so it becomes a pre-processor define), then you won’t need to write lines like this:

document[key.c_str()]

but instead just use the std::string without getting the raw pointer:

document[key]

Many thanks for the help, but I don’t think defensive programming is the solution here, I have control over the json files, so I know the translations will exist, both the key and value. Also, if I can’t load the translations the game should exit, so returning an empty string I think is not going to help…

What I really need to do is to get to the bottom of this, find the root cause of crash and try to fix it… could it be possible that the json file is not properly loaded? not having a device for debugging really sucks… :frowning:

Would you guys think I should probably throw an exception if the json file does not load? Here:

if(document.HasParseError())
    {
        CCLOG("Language file parsing error!");
        return;
    }

Thanks again for all the help.

How about forcing the error in your dev environment, and by that I mean introduce all the possible incorrect conditions you can think of to try to replicate that exact error. At least then you would have narrowed down the possible reasons or section of code that is causing it.

I basically have the same code structure as this post suggest. But i prefer using plist. And I don’t understand why is the switch code used.

{
        std::string localstr = "localization/" + std::string(Application::getInstance()->getCurrentLanguageCode()) + "/GameData.plist";
        if(FileUtils::getInstance()->isFileExist(localstr)){
            mainValueMap = FileUtils::getInstance()->getValueMapFromFile(localstr);
            log("Found File");
            
        }
        else{
            mainValueMap = FileUtils::getInstance()->getValueMapFromFile("localization/en/GameData.plist");
            log("Couldn't find File ... using english");
            
            
        }
    }

It turns out some defensive programming could’ve helped. The problem was that the json file was not being parsed properly because the XCode was not linking the Spanish translations properly, that is why it was failing in some devices (the ones in English were just fine), and also why it happened only on iOS (Android does not have all this linking non-sense).

Thanks very much everyone for the help! Problem solved!

1 Like