Skip to content

Commit

Permalink
#5231: Move the language initialisation to a later point in time, let…
Browse files Browse the repository at this point in the history
…'s see if this is good enough.
  • Loading branch information
codereader committed Aug 14, 2020
1 parent ff12745 commit 7cd49ba
Show file tree
Hide file tree
Showing 8 changed files with 376 additions and 18 deletions.
26 changes: 20 additions & 6 deletions include/i18n.h
Expand Up @@ -3,18 +3,34 @@
#include <string>
#include "imodule.h"

class ILocalisationProvider
{
public:
virtual ~ILocalisationProvider() {}

typedef std::shared_ptr<ILocalisationProvider> Ptr;

// Returns the localised version of the given string
virtual std::string getLocalisedString(const char* stringToLocalise) = 0;
};

class ILanguageManager :
public RegisterableModule
{
public:
virtual ~ILanguageManager() {}

/**
* Returns the localized version of the given input string
* or the unmodified string if no suitable localization provider
* Registers the given provider, which will be used to resolve localised strings.
*/
virtual void registerProvider(const ILocalisationProvider::Ptr& instance) = 0;

/**
* Returns the localised version of the given input string
* or the unmodified string if no suitable localisation provider
* was found.
*/
virtual std::string getLocalizedString(const char* stringToLocalise) = 0;
virtual std::string getLocalisedString(const char* stringToLocalise) = 0;
};

const char* const MODULE_LANGUAGEMANAGER("LanguageManager");
Expand All @@ -38,8 +54,6 @@ inline ILanguageManager& GlobalLanguageManager()
#define WXINTL_NO_GETTEXT_MACRO
#endif

//#include <wx/intl.h>

// Custom translation macros _, N_ and C_

inline std::string _(const char* s)
Expand All @@ -54,7 +68,7 @@ inline std::string _(const char* s)
return s; // still too early
}

return GlobalLanguageManager().getLocalizedString(s);
return GlobalLanguageManager().getLocalisedString(s);
}

// Macro used to decorate a string as localizable, such that it is recognised
Expand Down
236 changes: 236 additions & 0 deletions radiant/LocalisationModule.cpp
@@ -0,0 +1,236 @@
#include "LocalisationModule.h"

#include <fstream>
#include <stdexcept>

#include "itextstream.h"
#include "ipreferencesystem.h"
#include "registry/registry.h"

#include "string/case_conv.h"
#include "os/path.h"
#include "module/StaticModule.h"

#include <wx/translation.h>

namespace ui
{

namespace
{
const char* const LANGUAGE_SETTING_FILE = "darkradiant.language";
const char* const DEFAULT_LANGUAGE = "en";
const std::string RKEY_LANGUAGE("user/ui/language");
}

class UnknownLanguageException :
public std::runtime_error
{
public:
UnknownLanguageException(const std::string& what) :
std::runtime_error(what)
{}
};

// Local helper class acting as ILocalisationProvider
// To resolve the localised strings, it dispatches the call to the wx framework
class LocalisationProviderWx :
public ILocalisationProvider
{
public:
std::string getLocalisedString(const char* stringToLocalise) override
{
return wxGetTranslation(stringToLocalise).ToStdString();
}
};

LocalisationModule::LocalisationModule()
{} // constructor needed for wxLocale pimpl

LocalisationModule::~LocalisationModule()
{} // destructor needed for wxLocale pimpl

const std::string& LocalisationModule::getName() const
{
static std::string _name("LocalisationProviders");
return _name;
}

const StringSet& LocalisationModule::getDependencies() const
{
static StringSet _dependencies;

if (_dependencies.empty())
{
_dependencies.insert(MODULE_LANGUAGEMANAGER);
_dependencies.insert(MODULE_PREFERENCESYSTEM);
_dependencies.insert(MODULE_XMLREGISTRY);
}

return _dependencies;
}

void LocalisationModule::initialiseModule(const ApplicationContext& ctx)
{
rMessage() << getName() << "::initialiseModule called" << std::endl;

// Register the provider with the core
GlobalLanguageManager().registerProvider(std::make_shared<LocalisationProviderWx>());

// Point wxWidgets to the folder where the catalog files are stored
_i18nPath = os::standardPathWithSlash(ctx.getApplicationPath() + "i18n");
wxFileTranslationsLoader::AddCatalogLookupPathPrefix(_i18nPath);

// Load the persisted language setting
_languageSettingFile = ctx.getSettingsPath() + LANGUAGE_SETTING_FILE;
_curLanguage = loadLanguageSetting();

rMessage() << "Current language setting: " << _curLanguage << std::endl;

// Fill array of supported languages
loadSupportedLanguages();

// Fill array of available languages
findAvailableLanguages();

// Keep locale set to "C" for faster stricmp in Windows builds
_wxLocale.reset(new wxLocale(_curLanguage, _curLanguage, "C"));
_wxLocale->AddCatalog(GETTEXT_PACKAGE);

int curLangIndex = 0; // english

try
{
int index = getLanguageIndex(_curLanguage);

// Get the offset into the array of available languages
auto found = std::find(_availableLanguages.begin(), _availableLanguages.end(), index);

if (found != _availableLanguages.end())
{
curLangIndex = static_cast<int>(std::distance(_availableLanguages.begin(), found));
}
}
catch (UnknownLanguageException&)
{
rWarning() << "Warning, unknown language found in " <<
LANGUAGE_SETTING_FILE << ", reverting to English" << std::endl;
}

// Construct the list of available languages
ComboBoxValueList langs;

for (int code : _availableLanguages)
{
const Language& lang = _supportedLanguages[code];
langs.emplace_back(lang.twoDigitCode + " - " + lang.displayName);
}

// Load the currently selected index into the registry
registry::setValue(RKEY_LANGUAGE, curLangIndex);
GlobalRegistry().setAttribute(RKEY_LANGUAGE, "volatile", "1"); // don't save this to user.xml

// Add Preferences
IPreferencePage& page = GlobalPreferenceSystem().getPage(_("Settings/Language"));
page.appendCombo(_("Language"), RKEY_LANGUAGE, langs);

page.appendLabel(_("<b>Note:</b> You'll need to restart DarkRadiant\nafter changing the language setting."));
}

void LocalisationModule::shutdownModule()
{
// Get the language setting from the registry (this is an integer)
// and look up the language code (two digit)
int langNum = registry::getValue<int>(RKEY_LANGUAGE);

assert(langNum >= 0 && langNum < static_cast<int>(_availableLanguages.size()));

// Look up the language index in the list of available languages
int langIndex = _availableLanguages[langNum];

assert(_supportedLanguages.find(langIndex) != _supportedLanguages.end());

// Save the language code to the settings file
saveLanguageSetting(_supportedLanguages[langIndex].twoDigitCode);
}

std::string LocalisationModule::loadLanguageSetting()
{
std::string language = DEFAULT_LANGUAGE;

// Check for an existing language setting file in the user settings folder
std::ifstream str(_languageSettingFile.c_str());

if (str.good())
{
str >> language;
}

return language;
}

void LocalisationModule::saveLanguageSetting(const std::string& language)
{
std::ofstream str(_languageSettingFile.c_str());

str << language;

str.flush();
str.close();
}

void LocalisationModule::loadSupportedLanguages()
{
_supportedLanguages.clear();

int index = 0;

_supportedLanguages[index++] = Language("en", _("English"));
_supportedLanguages[index++] = Language("de", _("German"));
}

void LocalisationModule::findAvailableLanguages()
{
// English (index 0) is always available
_availableLanguages.push_back(0);

wxFileTranslationsLoader loader;
wxArrayString translations = loader.GetAvailableTranslations(GETTEXT_PACKAGE);

for (const auto& lang : translations)
{
// Get the index (is this a known language?)
try
{
int index = getLanguageIndex(lang.ToStdString());

// Add this to the list (could use more extensive checking, but this is enough for now)
_availableLanguages.push_back(index);
}
catch (UnknownLanguageException&)
{
rWarning() << "Skipping unknown language: " << lang << std::endl;
}
}

rMessage() << "Found " << _availableLanguages.size() << " language folders." << std::endl;
}

int LocalisationModule::getLanguageIndex(const std::string& languageCode)
{
std::string code = string::to_lower_copy(languageCode);

for (const auto& pair : _supportedLanguages)
{
if (pair.second.twoDigitCode == code)
{
return pair.first;
}
}

throw UnknownLanguageException("Unknown language: " + languageCode);
}

module::StaticModule<LocalisationModule> localisationModule;

}
69 changes: 69 additions & 0 deletions radiant/LocalisationModule.h
@@ -0,0 +1,69 @@
#pragma once

#include "i18n.h"

namespace ui
{

class LocalisationModule :
public RegisterableModule
{
private:
// The current language string (e.g. "en_US")
std::string _curLanguage;
std::string _languageSettingFile;

// The path where all the languages are stored
std::string _i18nPath;

std::unique_ptr<wxLocale> _wxLocale;

struct Language
{
std::string twoDigitCode; // "en"
std::string displayName; // "English"

Language()
{}

Language(const std::string& twoDigitCode_, const std::string& displayName_) :
twoDigitCode(twoDigitCode_),
displayName(displayName_)
{}
};

// Supported languages
typedef std::map<int, Language> LanguageMap;
LanguageMap _supportedLanguages;

// Available languages (for the preference page)
typedef std::vector<int> LanguageList;
LanguageList _availableLanguages;

public:
LocalisationModule();
~LocalisationModule();

const std::string& getName() const override;
const StringSet& getDependencies() const;
void initialiseModule(const ApplicationContext& ctx) override;
void shutdownModule() override;

private:
// Loads the language setting from the .language in the user settings folder
std::string loadLanguageSetting();

// Saves the language setting to the .language in the user settings folder
void saveLanguageSetting(const std::string& language);

// Fills the array of supported languages
void loadSupportedLanguages();

// Searches for available language .mo files
void findAvailableLanguages();

// Returns the language index for the given two-digit code
int getLanguageIndex(const std::string& languageCode);
};

}
2 changes: 2 additions & 0 deletions radiantcore/Radiant.cpp
Expand Up @@ -37,10 +37,12 @@ Radiant::Radiant(ApplicationContext& context) :
_moduleRegistry.reset(new module::ModuleRegistry);
_moduleRegistry->setContext(_context);

#if 0
#ifndef POSIX
// Initialise the language based on the settings in the user settings folder
language::LanguageManager().init(_context);
#endif
#endif
}

Radiant::~Radiant()
Expand Down

0 comments on commit 7cd49ba

Please sign in to comment.