1
0
forked from 0ad/0ad

Internationalization of the C++ side

Provides the logic to detect the system language, load translations, and
use
loaded translations both in the C++ and the JavaScript side.

This patch includes code by Yves, sanderd17, leper, historic_bruno and
Gallaecio. It’s worth noting that Yves and historic_bruno were also the
main
contributors behind the changes in 1b3261b8f4 as well.

This was SVN commit r14953.
This commit is contained in:
Adrián Chaves 2014-04-20 20:03:57 +00:00
parent 30d5b572a6
commit d6db5a466d
14 changed files with 1133 additions and 43 deletions

View File

@ -16,6 +16,7 @@ else
end
-- directory for shared, bundled libraries
libraries_source_dir = rootdir.."/libraries/source/"
third_party_source_dir = rootdir.."/source/third_party/"
local function add_default_lib_paths(extern_lib)
libdirs { libraries_dir .. extern_lib .. "/lib" }
@ -33,6 +34,10 @@ local function add_source_include_paths(extern_lib)
includedirs { libraries_source_dir .. extern_lib .. "/include" }
end
local function add_third_party_include_paths(extern_lib)
includedirs { third_party_source_dir .. extern_lib .. "/include" }
end
-- For unixes: add buildflags and linkflags using pkg-config or another similar command.
-- By default, pkg-config is used. Other commands can be passed as "alternative_cmd".
@ -359,6 +364,58 @@ extern_lib_defs = {
end
end,
},
iconv = {
compile_settings = function()
if os.is("windows") then
add_default_include_paths("iconv")
defines { "HAVE_ICONV_CONST" }
defines { "LIBICONV_STATIC" }
end
end,
link_settings = function()
if os.is("windows") then
add_default_lib_paths("iconv")
end
add_default_links({
win_names = { "iconv" },
-- TODO: glibc provides symbols for this, so we should only include that (and depend on libiconv) on non-glibc unix
--unix_names = { "iconv" },
dbg_suffix = "",
})
end,
},
icu = {
compile_settings = function()
if os.is("windows") then
add_default_include_paths("icu")
elseif os.is("macosx") then
-- Support ICU_CONFIG for overriding the default PATH-based icu-config
icu_config_path = os.getenv("ICU_CONFIG")
if not icu_config_path then
icu_config_path = "icu-config"
end
pkgconfig_cflags(nil, icu_config_path.." --cppflags")
end
end,
link_settings = function()
if os.is("windows") then
add_default_lib_paths("icu")
end
if os.is("macosx") then
icu_config_path = os.getenv("ICU_CONFIG")
if not icu_config_path then
icu_config_path = "gloox-config"
end
pkgconfig_libs(nil, icu_config_path.." --ldflags-searchpath --ldflags-libsonly --ldflags-system")
else
add_default_links({
win_names = { "icuuc", "icuin" },
unix_names = { "icui18n", "icuuc" },
dbg_suffix = "",
})
end
end,
},
libcurl = {
compile_settings = function()
if os.is("windows") or os.is("macosx") then
@ -603,6 +660,11 @@ extern_lib_defs = {
end
end,
},
tinygettext = {
compile_settings = function()
add_third_party_include_paths("tinygettext")
end,
},
valgrind = {
compile_settings = function()
add_source_include_paths("valgrind")

View File

@ -542,6 +542,12 @@ function setup_static_lib_project (project_name, rel_source_dirs, extern_libs, e
end
end
function setup_third_party_static_lib_project (project_name, rel_source_dirs, extern_libs, extra_params)
setup_static_lib_project(project_name, rel_source_dirs, extern_libs, extra_params)
includedirs { source_root .. "third_party/" .. project_name .. "/include/" .. project_name }
end
function setup_shared_lib_project (project_name, rel_source_dirs, extern_libs, extra_params)
local target_type = "SharedLib"
@ -586,6 +592,27 @@ function setup_all_libs ()
end
setup_static_lib_project("network", source_dirs, extern_libs, {})
source_dirs = {
"third_party/tinygettext/src",
}
extern_libs = {
"iconv",
}
setup_third_party_static_lib_project("tinygettext", source_dirs, extern_libs, { no_pch = 1 })
-- it's an external library and we don't want to modify its source to fix warnings, so we just disable them to avoid noise in the compile output
if _ACTION == "vs2005" or _ACTION == "vs2008" or _ACTION == "vs2010" or _ACTION == "vs2012" or _ACTION == "vs2013" then
buildoptions {
"/wd4127",
"/wd4309",
"/wd4800",
"/wd4100",
"/wd4996",
"/wd4099",
"/wd4503"
}
end
if not _OPTIONS["without-lobby"] then
source_dirs = {
@ -673,6 +700,8 @@ function setup_all_libs ()
"soundmanager/scripting",
"maths",
"maths/scripting",
"i18n",
"i18n/scripting"
}
extern_libs = {
"spidermonkey",
@ -683,6 +712,9 @@ function setup_all_libs ()
"boost",
"enet",
"libcurl",
"tinygettext",
"icu",
"iconv",
}
if not _OPTIONS["without-audio"] then
@ -727,13 +759,17 @@ function setup_all_libs ()
source_dirs = {
"gui",
"gui/scripting"
"gui/scripting",
"i18n"
}
extern_libs = {
"spidermonkey",
"sdl", -- key definitions
"opengl",
"boost",
"tinygettext",
"icu",
"iconv",
}
if not _OPTIONS["without-audio"] then
table.insert(extern_libs, "openal")
@ -858,6 +894,9 @@ used_extern_libs = {
"comsuppw",
"enet",
"libcurl",
"tinygettext",
"icu",
"iconv",
"valgrind",
}

View File

@ -47,8 +47,10 @@ CGUI
#include "graphics/TextRenderer.h"
#include "lib/input.h"
#include "lib/bits.h"
#include "i18n/L10n.h"
#include "lib/timer.h"
#include "lib/sysdep/sysdep.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Hotkey.h"
@ -1095,6 +1097,10 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec
ELMT(object);
ELMT(action);
ELMT(repeat);
ELMT(translatableAttribute);
ELMT(translate);
ELMT(attribute);
ELMT(keep);
ATTR(style);
ATTR(type);
ATTR(name);
@ -1102,6 +1108,7 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec
ATTR(z);
ATTR(on);
ATTR(file);
ATTR(id);
//
// Read Style and set defaults
@ -1242,8 +1249,27 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec
code = scriptfile.DecodeUTF8(); // assume it's UTF-8
}
// Read the inline code (concatenating to the file code, if both are specified)
code += CStr(child.GetText());
XMBElementList grandchildren = child.GetChildNodes();
if (grandchildren.Count > 0) // The <action> element contains <keep> and <translate> tags.
{
for (int i = 0; i < grandchildren.Count; ++i)
{
XMBElement grandchild = grandchildren.Item(i);
if (grandchild.GetNodeName() == elmt_translate)
{
code += L10n::Instance().Translate(grandchild.GetText());
}
else if (grandchild.GetNodeName() == elmt_keep)
{
code += grandchild.GetText();
}
}
}
else // It’s pure JavaScript code.
{
// Read the inline code (concatenating to the file code, if both are specified)
code += CStr(child.GetText());
}
CStr action = CStr(child.GetAttributes().GetNamedItem(attr_on));
@ -1255,6 +1281,53 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec
{
Xeromyces_ReadRepeat(child, pFile, object, Paths);
}
else if (element_name == elmt_translatableAttribute)
{
// This is an element in the form “<translatableAttribute id="attributeName">attributeValue</translatableAttribute>”.
CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name.
if (!attributeName.empty())
{
CStr value(child.GetText());
if (!value.empty())
{
CStr translatedValue(L10n::Instance().Translate(value));
object->SetSetting(attributeName, translatedValue.UnescapeBackslashes().FromUTF8(), true);
}
}
else // Ignore.
{
LOGERROR(L"GUI: ‘attribute’ XML element with empty ‘id’ XML attribute found. (object: %hs)", object->GetPresentableName().c_str());
}
}
else if (element_name == elmt_attribute)
{
// This is an element in the form “<attribute id="attributeName"><keep>Don’t translate this part
// </keep><translate>but translate this one.</translate></attribute>”.
CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name.
if (!attributeName.empty())
{
CStr translatedValue;
XMBElementList grandchildren = child.GetChildNodes();
for (int i = 0; i < grandchildren.Count; ++i)
{
XMBElement grandchild = grandchildren.Item(i);
if (grandchild.GetNodeName() == elmt_translate)
{
translatedValue += L10n::Instance().Translate(grandchild.GetText());
}
else if (grandchild.GetNodeName() == elmt_keep)
{
translatedValue += grandchild.GetText();
}
}
object->SetSetting(attributeName, translatedValue.UnescapeBackslashes().FromUTF8(), true);
}
else // Ignore.
{
LOGERROR(L"GUI: ‘attribute’ XML element with empty ‘id’ XML attribute found. (object: %hs)", object->GetPresentableName().c_str());
}
}
else
{
// Try making the object read the tag.

View File

@ -1,5 +1,6 @@
#include "precompiled.h"
#include "COList.h"
#include "i18n/L10n.h"
#include "ps/CLogger.h"
@ -121,9 +122,13 @@ void COList::HandleMessage(SGUIMessage &Message)
bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile)
{
int elmt_item = pFile->GetElementID("item");
int elmt_heading = pFile->GetElementID("heading");
int elmt_def = pFile->GetElementID("def");
#define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
#define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
ELMT(item);
ELMT(heading);
ELMT(def);
ELMT(translatableAttribute);
ATTR(id);
if (child.GetNodeName() == elmt_item)
{
@ -180,6 +185,30 @@ bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile
}
XMBElementList grandchildren = child.GetChildNodes();
for (int i = 0; i < grandchildren.Count; ++i)
{
XMBElement grandchild = grandchildren.Item(i);
if (grandchild.GetNodeName() == elmt_translatableAttribute)
{
CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id));
// only the heading is translatable for list defs
if (!attributeName.empty() && attributeName == "heading")
{
CStr value(grandchild.GetText());
if (!value.empty())
{
CStr translatedValue(L10n::Instance().Translate(value));
oDef.m_Heading = translatedValue.FromUTF8();
}
}
else // Ignore.
{
LOGERROR(L"GUI: translatable attribute in olist def that isn't a heading. (object: %hs)", this->GetPresentableName().c_str());
}
}
}
m_ObjectsDefs.push_back(oDef);
AddSetting(GUIST_CGUIList, oDef.m_Id);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -22,6 +22,7 @@
#include "graphics/ShaderManager.h"
#include "graphics/TextureManager.h"
#include "gui/GUIutil.h"
#include "i18n/L10n.h"
#include "lib/ogl.h"
#include "lib/utf8.h"
#include "lib/res/h_mgr.h"
@ -200,7 +201,7 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, const CStr& SpriteName,
if (!(*cit)->m_TextureName.empty())
{
CTextureProperties textureProps((*cit)->m_TextureName);
CTextureProperties textureProps(L10n::Instance().LocalizePath((*cit)->m_TextureName));
textureProps.SetWrap((*cit)->m_WrapMode);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();

View File

@ -52,6 +52,36 @@ void CGUIString::SFeedback::Reset()
m_NewLine=false;
}
// TODO this method should be removed at one point
// It is a solution for CParser not recognising backslash-escaped quoted
CStrW UnescapeString(CStrW originalString)
{
// no reason to mess with more memory if there is nothing to unescape
if (originalString.Find('&') == -1)
return originalString;
// clone the string
CStrW newString = CStrW();
for (ulong i; i < originalString.length(); ++i)
{
if (originalString[i] == '&' && originalString[i+1] == '#')
{
i += 2;
wchar_t c = 0;
while (originalString[i] >= '0' && originalString[i] <= '9')
{
c = c*10 + (originalString[i] - '0');
++i;
}
ENSURE(originalString[i] == ';');
if (c >= ' ')
newString += c;
}
else
newString += originalString[i];
}
return newString;
}
void CGUIString::GenerateTextCall(const CGUI *pGUI,
SFeedback &Feedback,
CStrIntern DefaultFont,
@ -213,7 +243,7 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
TextCall.m_UseCustomColor = false;
// Extract substd::string from RawString.
TextCall.m_String = GetRawString().substr(_from, _to-_from);
TextCall.m_String = UnescapeString(GetRawString().substr(_from, _to-_from));
// Go through tags and apply changes.
std::vector<CGUIString::TextChunk::Tag>::const_iterator it2;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -27,10 +27,12 @@
#include "gui/IGUIObject.h"
#include "gui/scripting/JSInterface_GUITypes.h"
#include "graphics/scripting/JSInterface_GameView.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "i18n/L10n.h"
#include "i18n/scripting/JSInterface_L10n.h"
#include "lib/svn_revision.h"
#include "lib/sysdep/sysdep.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lobby/scripting/JSInterface_Lobby.h"
#include "maths/FixedVector3D.h"
#include "network/NetClient.h"
@ -54,7 +56,6 @@
#include "ps/UserReport.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/Config.h"
#include "ps/ConfigDB.h"
#include "renderer/scripting/JSInterface_Renderer.h"
#include "tools/atlas/GameInterface/GameLoop.h"
@ -696,28 +697,53 @@ CScriptVal GetGUIObjectByName(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CS
// lib/svn_revision.cpp. it is useful to know when attempting to
// reproduce bugs (the main EXE and PDB should be temporarily reverted to
// that revision so that they match user-submitted crashdumps).
CStr GetBuildTimestamp(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int mode)
std::wstring GetBuildTimestamp(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int mode)
{
char buf[200];
// see function documentation
switch(mode)
if (mode == -1) // Date, time and revision.
{
case -1:
sprintf_s(buf, ARRAY_SIZE(buf), "%s %s (%ls)", __DATE__, __TIME__, svn_revision);
break;
case 0:
sprintf_s(buf, ARRAY_SIZE(buf), "%s", __DATE__);
break;
case 1:
sprintf_s(buf, ARRAY_SIZE(buf), "%s", __TIME__);
break;
case 2:
sprintf_s(buf, ARRAY_SIZE(buf), "%ls", svn_revision);
break;
UDate dateTime = L10n::Instance().ParseDateTime(__DATE__ " " __TIME__, "MMM d yyyy HH:mm:ss", Locale::getUS());
std::string dateTimeString = L10n::Instance().LocalizeDateTime(dateTime, L10n::DateTime, SimpleDateFormat::DATE_TIME);
char svnRevision[32];
sprintf_s(svnRevision, ARRAY_SIZE(svnRevision), "%ls", svn_revision);
if (strcmp(svnRevision, "custom build") == 0)
{
// Translation: First item is a date and time, item between parenthesis is the Subversion revision number of the current build.
sprintf_s(buf, ARRAY_SIZE(buf), L10n::Instance().Translate("%s (custom build)").c_str(), dateTimeString.c_str());
}
else
{
// Translation: First item is a date and time, item between parenthesis is the Subversion revision number of the current build.
sprintf_s(buf, ARRAY_SIZE(buf), L10n::Instance().Translate("%s (%ls)").c_str(), dateTimeString.c_str(), svn_revision);
}
}
else if (mode == 0) // Date.
{
UDate dateTime = L10n::Instance().ParseDateTime(__DATE__, "MMM d yyyy", Locale::getUS());
std::string dateTimeString = L10n::Instance().LocalizeDateTime(dateTime, L10n::Date, SimpleDateFormat::MEDIUM);
sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
}
else if (mode == 1) // Time.
{
UDate dateTime = L10n::Instance().ParseDateTime(__TIME__, "HH:mm:ss", Locale::getUS());
std::string dateTimeString = L10n::Instance().LocalizeDateTime(dateTime, L10n::Time, SimpleDateFormat::MEDIUM);
sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
}
else if (mode == 2) // Revision.
{
char svnRevision[32];
sprintf_s(svnRevision, ARRAY_SIZE(svnRevision), "%ls", svn_revision);
if (strcmp(svnRevision, "custom build") == 0)
{
sprintf_s(buf, ARRAY_SIZE(buf), L10n::Instance().Translate("custom build").c_str());
}
else
{
sprintf_s(buf, ARRAY_SIZE(buf), "%ls", svn_revision);
}
}
return CStr(buf);
return wstring_from_utf8(buf);
}
//-----------------------------------------------------------------------------
@ -773,7 +799,6 @@ void StartJsTimer(ScriptInterface::CxPrivate* pCxPrivate, unsigned int slot)
js_start_times[slot].SetFromTimer();
}
void StopJsTimer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), unsigned int slot)
{
if (slot >= MAX_JS_TIMERS)
@ -785,9 +810,6 @@ void StopJsTimer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), unsigned int sl
BillingPolicy_Default()(&js_timer_clients[slot], js_start_times[slot], now);
js_start_times[slot].SetToZero();
}
} // namespace
@ -796,12 +818,13 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
{
JSI_IGUIObject::init(scriptInterface);
JSI_GUITypes::init(scriptInterface);
JSI_GameView::RegisterScriptFunctions(scriptInterface);
JSI_Renderer::RegisterScriptFunctions(scriptInterface);
JSI_Console::RegisterScriptFunctions(scriptInterface);
JSI_ConfigDB::RegisterScriptFunctions(scriptInterface);
JSI_Sound::RegisterScriptFunctions(scriptInterface);
JSI_L10n::RegisterScriptFunctions(scriptInterface);
// VFS (external)
scriptInterface.RegisterFunction<CScriptVal, std::wstring, std::wstring, bool, &JSI_VFS::BuildDirEntList>("BuildDirEntList");
@ -876,7 +899,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<bool, &IsPaused>("IsPaused");
scriptInterface.RegisterFunction<void, bool, &SetPaused>("SetPaused");
scriptInterface.RegisterFunction<int, &GetFps>("GetFPS");
scriptInterface.RegisterFunction<CStr, int, &GetBuildTimestamp>("BuildTime");
scriptInterface.RegisterFunction<std::wstring, int, &GetBuildTimestamp>("BuildTime");
// User report functions
scriptInterface.RegisterFunction<bool, &IsUserReportEnabled>("IsUserReportEnabled");

486
source/i18n/L10n.cpp Normal file
View File

@ -0,0 +1,486 @@
/* Copyright (c) 2014 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "precompiled.h"
#include "i18n/L10n.h"
#include <iostream>
#include <string>
#include <boost/concept_check.hpp>
#include "lib/file/file_system.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/GameSetup/GameSetup.h"
L10n& L10n::Instance()
{
static L10n m_instance;
return m_instance;
}
L10n::L10n()
: currentLocaleIsOriginalGameLocale(false), dictionary(new tinygettext::Dictionary())
{
LoadListOfAvailableLocales();
ReevaluateCurrentLocaleAndReload();
}
L10n::~L10n()
{
for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
delete *iterator;
delete dictionary;
}
Locale L10n::GetCurrentLocale()
{
return currentLocale;
}
bool L10n::SaveLocale(const std::string& localeCode)
{
return SaveLocale(Locale(Locale::createCanonical(localeCode.c_str())));
}
bool L10n::SaveLocale(Locale locale)
{
// TODO: Use the ConfigDB functions exposed to js to change the config value
// Save the new locale in the settings file.
if (!ValidateLocale(locale))
return false;
g_ConfigDB.SetValueString(CFG_USER, "locale", locale.getName());
g_ConfigDB.WriteFile(CFG_USER);
return true;
}
bool L10n::ValidateLocale(const std::string& localeCode)
{
return ValidateLocale(Locale::createCanonical(localeCode.c_str()));
}
// Returns true if both of these conditions are true:
// 1. ICU has resources for that locale (which also ensures it's a valid locale string)
// 2. Either a dictionary for language_country or for language is available.
bool L10n::ValidateLocale(Locale locale)
{
int32_t count;
bool icuSupported = false;
const Locale* icuSupportedLocales = Locale::getAvailableLocales(count);
for (int i=0; i<count; ++i)
{
if (icuSupportedLocales[i] == locale)
{
icuSupported = true;
break;
}
}
if(!icuSupported)
return false;
for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
{
if ((strcmp((*iterator)->getLanguage(), locale.getLanguage()) == 0 && strcmp((*iterator)->getCountry(), "") == 0) ||
(strcmp((*iterator)->getLanguage(), locale.getLanguage()) == 0 && strcmp((*iterator)->getCountry(), locale.getCountry()) == 0))
{
return true;
}
}
return false;
}
std::vector<std::wstring> L10n::GetDictionariesForDictLocale(const std::string& locale)
{
std::wstring tmpLocale(locale.begin(), locale.end());
std::vector<std::wstring> ret;
VfsPaths filenames;
if (vfs::GetPathnames(g_VFS, L"l10n/", tmpLocale.append(L".*.po").c_str(), filenames) < 0)
return ret;
if (filenames.size() == 0)
{
Locale tmpLocale1 = Locale::createCanonical(locale.c_str());
if (vfs::GetPathnames(g_VFS, L"l10n/", wstring_from_utf8(tmpLocale1.getLanguage()).append(L".*.po").c_str(), filenames) < 0)
return ret;
}
for (VfsPaths::iterator it = filenames.begin(); it != filenames.end(); ++it)
{
VfsPath filepath = *it;
ret.push_back(filepath.Filename().string());
}
return ret;
}
std::string L10n::GetDictionaryLocale(std::string configLocaleString)
{
Locale out;
GetDictionaryLocale(configLocaleString, out);
return out.getName();
}
// First, try to get a valid locale from the config, then check if the system locale can be used and otherwise fall back to en_US.
void L10n::GetDictionaryLocale(std::string configLocaleString, Locale& outLocale)
{
bool validConfigLocale = false;
if (!configLocaleString.empty())
{
Locale configLocale = Locale::createCanonical(configLocaleString.c_str());
if (ValidateLocale(configLocale))
{
outLocale = configLocale;
validConfigLocale = true;
}
else
LOGWARNING(L"The configured locale is not valid or no translations are available. Falling back to another locale.");
}
if (!validConfigLocale)
{
Locale systemLocale = Locale::getDefault();
if (ValidateLocale(systemLocale))
{
outLocale = systemLocale;
}
else
{
outLocale = Locale::getUS();
}
}
}
// Try to find the best disctionary locale based on user configuration and system locale, set the currentLocale and reload the dictionary.
void L10n::ReevaluateCurrentLocaleAndReload()
{
std::string locale;
CFG_GET_VAL("locale", String, locale);
GetDictionaryLocale(locale, currentLocale);
currentLocaleIsOriginalGameLocale = (currentLocale == Locale::getUS()) == TRUE;
LoadDictionaryForCurrentLocale();
}
// Get all locales supported by ICU.
std::vector<std::string> L10n::GetAllLocales()
{
std::vector<std::string> ret;
int32_t count;
const Locale* icuSupportedLocales = Locale::getAvailableLocales(count);
for (int i=0; i<count; ++i)
ret.push_back(icuSupportedLocales[i].getName());
return ret;
}
std::vector<std::string> L10n::GetSupportedLocaleBaseNames()
{
std::vector<std::string> supportedLocaleCodes;
for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
{
if (!InDevelopmentCopy() && strcmp((*iterator)->getBaseName(), "long") == 0)
continue;
supportedLocaleCodes.push_back((*iterator)->getBaseName());
}
return supportedLocaleCodes;
}
std::vector<std::wstring> L10n::GetSupportedLocaleDisplayNames()
{
std::vector<std::wstring> supportedLocaleDisplayNames;
for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
{
if (strcmp((*iterator)->getBaseName(), "long") == 0)
{
if (InDevelopmentCopy())
supportedLocaleDisplayNames.push_back(wstring_from_utf8(Translate("Long strings")));
continue;
}
UnicodeString utf16LocaleDisplayName;
(**iterator).getDisplayName(**iterator, utf16LocaleDisplayName);
char localeDisplayName[512];
CheckedArrayByteSink sink(localeDisplayName, ARRAY_SIZE(localeDisplayName));
utf16LocaleDisplayName.toUTF8(sink);
ENSURE(!sink.Overflowed());
supportedLocaleDisplayNames.push_back(wstring_from_utf8(std::string(localeDisplayName, sink.NumberOfBytesWritten())));
}
return supportedLocaleDisplayNames;
}
std::string L10n::GetCurrentLocaleString()
{
return currentLocale.getName();
}
std::string L10n::GetLocaleLanguage(const std::string& locale)
{
Locale loc = Locale::createCanonical(locale.c_str());
return loc.getLanguage();
}
std::string L10n::GetLocaleBaseName(const std::string& locale)
{
Locale loc = Locale::createCanonical(locale.c_str());
return loc.getBaseName();
}
std::string L10n::GetLocaleCountry(const std::string& locale)
{
Locale loc = Locale::createCanonical(locale.c_str());
return loc.getCountry();
}
std::string L10n::GetLocaleScript(const std::string& locale)
{
Locale loc = Locale::createCanonical(locale.c_str());
return loc.getScript();
}
std::string L10n::Translate(const std::string& sourceString)
{
if (!currentLocaleIsOriginalGameLocale)
return dictionary->translate(sourceString);
return sourceString;
}
std::string L10n::TranslateWithContext(const std::string& context, const std::string& sourceString)
{
if (!currentLocaleIsOriginalGameLocale)
return dictionary->translate_ctxt(context, sourceString);
return sourceString;
}
std::string L10n::TranslatePlural(const std::string& singularSourceString, const std::string& pluralSourceString, int number)
{
if (!currentLocaleIsOriginalGameLocale)
return dictionary->translate_plural(singularSourceString, pluralSourceString, number);
if (number == 1)
return singularSourceString;
return pluralSourceString;
}
std::string L10n::TranslatePluralWithContext(const std::string& context, const std::string& singularSourceString, const std::string& pluralSourceString, int number)
{
if (!currentLocaleIsOriginalGameLocale)
return dictionary->translate_ctxt_plural(context, singularSourceString, pluralSourceString, number);
if (number == 1)
return singularSourceString;
return pluralSourceString;
}
std::string L10n::TranslateLines(const std::string& sourceString)
{
std::string targetString;
std::stringstream stringOfLines(sourceString);
std::string line;
while (std::getline(stringOfLines, line)) {
targetString.append(Translate(line));
targetString.append("\n");
}
return targetString;
}
UDate L10n::ParseDateTime(const std::string& dateTimeString, const std::string& dateTimeFormat, const Locale& locale)
{
UErrorCode success = U_ZERO_ERROR;
UnicodeString utf16DateTimeString = UnicodeString::fromUTF8(dateTimeString.c_str());
UnicodeString utf16DateTimeFormat = UnicodeString::fromUTF8(dateTimeFormat.c_str());
DateFormat* dateFormatter = new SimpleDateFormat(utf16DateTimeFormat, locale, success);
UDate date = dateFormatter->parse(utf16DateTimeString, success);
delete dateFormatter;
return date;
}
std::string L10n::LocalizeDateTime(const UDate& dateTime, DateTimeType type, DateFormat::EStyle style)
{
UnicodeString utf16Date;
DateFormat* dateFormatter = CreateDateTimeInstance(type, style, currentLocale);
dateFormatter->format(dateTime, utf16Date);
char utf8Date[512];
CheckedArrayByteSink sink(utf8Date, ARRAY_SIZE(utf8Date));
utf16Date.toUTF8(sink);
ENSURE(!sink.Overflowed());
delete dateFormatter;
return std::string(utf8Date, sink.NumberOfBytesWritten());
}
std::string L10n::FormatMillisecondsIntoDateString(UDate milliseconds, const std::string& formatString)
{
UErrorCode success = U_ZERO_ERROR;
UnicodeString utf16Date;
UnicodeString utf16LocalizedDateTimeFormat = UnicodeString::fromUTF8(formatString.c_str());
// The format below should never reach the user, the one that matters is the
// one from the formatString parameter.
UnicodeString utf16SourceDateTimeFormat = UnicodeString::fromUTF8("No format specified (you should not be seeing this string!)");
SimpleDateFormat* dateFormatter = new SimpleDateFormat(utf16SourceDateTimeFormat, currentLocale, success);
dateFormatter->applyLocalizedPattern(utf16LocalizedDateTimeFormat, success);
dateFormatter->format(milliseconds, utf16Date);
delete dateFormatter;
char utf8Date[512];
CheckedArrayByteSink sink(utf8Date, ARRAY_SIZE(utf8Date));
utf16Date.toUTF8(sink);
ENSURE(!sink.Overflowed());
return std::string(utf8Date, sink.NumberOfBytesWritten());
}
std::string L10n::FormatDecimalNumberIntoString(double number)
{
UErrorCode success = U_ZERO_ERROR;
UnicodeString utf16Number;
NumberFormat* numberFormatter = NumberFormat::createInstance(currentLocale, UNUM_DECIMAL, success);
numberFormatter->format(number, utf16Number);
char utf8Number[512];
CheckedArrayByteSink sink(utf8Number, ARRAY_SIZE(utf8Number));
utf16Number.toUTF8(sink);
ENSURE(!sink.Overflowed());
return std::string(utf8Number, sink.NumberOfBytesWritten());
}
VfsPath L10n::LocalizePath(VfsPath sourcePath)
{
VfsPath path = sourcePath;
VfsPath localizedPath = sourcePath.Parent() / L"l10n" / wstring_from_utf8(currentLocale.getLanguage()) / sourcePath.Filename();
if (VfsFileExists(localizedPath))
path = localizedPath;
return path;
}
void L10n::LoadDictionaryForCurrentLocale()
{
delete dictionary;
dictionary = new tinygettext::Dictionary();
VfsPaths filenames;
if (vfs::GetPathnames(g_VFS, L"l10n/", (wstring_from_utf8(currentLocale.getBaseName()) + L".*.po").c_str(), filenames) < 0)
return;
// If not matching country is found, try to fall back to a matching language
if (filenames.size() == 0)
{
if (vfs::GetPathnames(g_VFS, L"l10n/", (wstring_from_utf8(currentLocale.getLanguage()) + L".*.po").c_str(), filenames) < 0)
return;
}
for (VfsPaths::iterator it = filenames.begin(); it != filenames.end(); ++it)
{
VfsPath filename = *it;
CVFSFile file;
file.Load(g_VFS, filename);
std::string content = file.DecodeUTF8();
ReadPoIntoDictionary(content, dictionary);
}
}
void L10n::LoadListOfAvailableLocales()
{
for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
delete *iterator;
availableLocales.clear();
Locale* defaultLocale = new Locale(Locale::getUS());
availableLocales.push_back(defaultLocale); // Always available.
VfsPaths filenames;
if (vfs::GetPathnames(g_VFS, L"l10n/", L"*.po", filenames) < 0)
return;
for (VfsPaths::iterator it = filenames.begin(); it != filenames.end(); ++it)
{
// Note: PO files follow this naming convention: “l10n/<locale code>.<mod name>.po”. For example: “l10n/gl.public.po”.
VfsPath filepath = *it;
std::string filename = utf8_from_wstring(filepath.string()).substr(strlen("l10n/"));
std::size_t lengthToFirstDot = filename.find('.');
std::string localeCode = filename.substr(0, lengthToFirstDot);
Locale* locale = new Locale(Locale::createCanonical(localeCode.c_str()));
bool localeIsAlreadyAvailable = false;
for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
{
if (*locale == **iterator)
{
localeIsAlreadyAvailable = true;
break;
}
}
if (!localeIsAlreadyAvailable)
availableLocales.push_back(locale);
else
delete locale;
}
}
void L10n::ReadPoIntoDictionary(const std::string& poContent, tinygettext::Dictionary* dictionary)
{
try
{
std::istringstream inputStream(poContent);
tinygettext::POParser::parse("virtual PO file", inputStream, *dictionary, false);
}
catch(std::exception& e)
{
LOGERROR(L"[Localization] Exception while reading virtual PO file: %hs", e.what());
}
}
DateFormat* L10n::CreateDateTimeInstance(L10n::DateTimeType type, DateFormat::EStyle style, const Locale& locale)
{
switch(type)
{
case Date:
return SimpleDateFormat::createDateInstance(style, locale);
case Time:
return SimpleDateFormat::createTimeInstance(style, locale);
case DateTime:
default:
return SimpleDateFormat::createDateTimeInstance(style, style, locale);
}
}

95
source/i18n/L10n.h Normal file
View File

@ -0,0 +1,95 @@
/* Copyright (c) 2014 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef L10N_H
#define L10N_H
#include <string>
#include <vector>
#include "lib/code_annotation.h"
#include "lib/external_libraries/icu.h"
#include "lib/external_libraries/tinygettext.h"
#include "lib/file/vfs/vfs_path.h"
class L10n
{
NONCOPYABLE(L10n);
public:
L10n();
~L10n();
enum DateTimeType { DateTime, Date, Time };
static L10n& Instance();
Locale GetCurrentLocale();
std::vector<std::string> GetAllLocales();
bool SaveLocale(const std::string& localeCode);
bool SaveLocale(Locale locale);
std::vector<std::string> GetSupportedLocaleBaseNames();
std::vector<std::wstring> GetSupportedLocaleDisplayNames();
std::string GetCurrentLocaleString();
std::string GetLocaleLanguage(const std::string& locale);
std::string GetLocaleBaseName(const std::string& locale);
std::string GetLocaleCountry(const std::string& locale);
std::string GetLocaleScript(const std::string& locale);
std::vector<std::wstring> GetDictionariesForDictLocale(const std::string& locale);
std::string GetDictionaryLocale(std::string configLocaleString);
void GetDictionaryLocale(std::string configLocaleString, Locale& outLocale);
void ReevaluateCurrentLocaleAndReload();
bool ValidateLocale(Locale locale);
bool ValidateLocale(const std::string& localeCode);
std::string Translate(const std::string& sourceString);
std::string TranslateWithContext(const std::string& context, const std::string& sourceString);
std::string TranslatePlural(const std::string& singularSourceString, const std::string& pluralSourceString, int number);
std::string TranslatePluralWithContext(const std::string& context, const std::string& singularSourceString, const std::string& pluralSourceString, int number);
std::string TranslateLines(const std::string& sourceString);
UDate ParseDateTime(const std::string& dateTimeString, const std::string& dateTimeFormat, const Locale& locale);
std::string LocalizeDateTime(const UDate& dateTime, DateTimeType type, DateFormat::EStyle style);
std::string FormatMillisecondsIntoDateString(UDate milliseconds, const std::string& formatString);
std::string FormatDecimalNumberIntoString(double number);
VfsPath LocalizePath(VfsPath sourcePath);
private:
tinygettext::Dictionary* dictionary;
bool isRtlLanguage;
Locale currentLocale;
std::vector<Locale*> availableLocales;
bool currentLocaleIsOriginalGameLocale;
Locale AutoDetectLocale();
void LoadDictionaryForCurrentLocale();
void LoadListOfAvailableLocales();
void ReadPoIntoDictionary(const std::string& poContent, tinygettext::Dictionary* dictionary);
DateFormat* CreateDateTimeInstance(DateTimeType type, DateFormat::EStyle style, const Locale& locale);
};
#endif // L10N_H

View File

@ -0,0 +1,178 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "JSInterface_L10n.h"
#include "i18n/L10n.h"
#include "lib/utf8.h"
#include "ps/ConfigDB.h"
// Returns a translation of the specified English string into the current language.
std::wstring JSI_L10n::Translate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring sourceString)
{
return wstring_from_utf8(L10n::Instance().Translate(utf8_from_wstring(sourceString)));
}
// Returns a translation of the specified English string, for the specified context.
std::wstring JSI_L10n::TranslateWithContext(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string context, std::wstring sourceString)
{
return wstring_from_utf8(L10n::Instance().TranslateWithContext(context, utf8_from_wstring(sourceString)));
}
// Return a translated version of the given strings (singular and plural) depending on an integer value.
std::wstring JSI_L10n::TranslatePlural(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring singularSourceString, std::wstring pluralSourceString, int number)
{
return wstring_from_utf8(L10n::Instance().TranslatePlural(utf8_from_wstring(singularSourceString), utf8_from_wstring(pluralSourceString), number));
}
// Return a translated version of the given strings (singular and plural) depending on an integer value, for the specified context.
std::wstring JSI_L10n::TranslatePluralWithContext(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string context, std::wstring singularSourceString, std::wstring pluralSourceString, int number)
{
return wstring_from_utf8(L10n::Instance().TranslatePluralWithContext(context, utf8_from_wstring(singularSourceString), utf8_from_wstring(pluralSourceString), number));
}
// Return a translated version of the given string, localizing it line by line.
std::wstring JSI_L10n::TranslateLines(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring sourceString)
{
return wstring_from_utf8(L10n::Instance().TranslateLines(utf8_from_wstring(sourceString)));
}
// Return a translated version of the items in the specified array.
std::vector<std::wstring> JSI_L10n::TranslateArray(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::vector<std::wstring> sourceArray)
{
std::vector<std::wstring> translatedArray;
for (std::vector<std::wstring>::iterator iterator = sourceArray.begin(); iterator != sourceArray.end(); ++iterator)
{
translatedArray.push_back(wstring_from_utf8(L10n::Instance().Translate(utf8_from_wstring(*iterator))));
}
return translatedArray;
}
// Return a translated version of the given decimal number.
std::wstring JSI_L10n::MarkToTranslate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring sourceString)
{
return sourceString;
}
// Return a localized version of a time given in milliseconds.
std::wstring JSI_L10n::FormatMillisecondsIntoDateString(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), UDate milliseconds, std::wstring formatString)
{
return wstring_from_utf8(L10n::Instance().FormatMillisecondsIntoDateString(milliseconds, utf8_from_wstring(formatString)));
}
// Return a localized version of the given decimal number.
std::wstring JSI_L10n::FormatDecimalNumberIntoString(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), double number)
{
return wstring_from_utf8(L10n::Instance().FormatDecimalNumberIntoString(number));
}
std::vector<std::string> JSI_L10n::GetSupportedLocaleBaseNames(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return L10n::Instance().GetSupportedLocaleBaseNames();
}
std::vector<std::wstring> JSI_L10n::GetSupportedLocaleDisplayNames(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return L10n::Instance().GetSupportedLocaleDisplayNames();
}
std::string JSI_L10n::GetCurrentLocale(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return L10n::Instance().GetCurrentLocaleString();
}
std::vector<std::string> JSI_L10n::GetAllLocales(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return L10n::Instance().GetAllLocales();
}
std::string JSI_L10n::GetDictionaryLocale(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string configLocale)
{
return L10n::Instance().GetDictionaryLocale(configLocale);
}
std::vector<std::wstring> JSI_L10n::GetDictionariesForDictLocale(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale)
{
return L10n::Instance().GetDictionariesForDictLocale(locale);
}
std::string JSI_L10n::GetLocaleLanguage(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale)
{
return L10n::Instance().GetLocaleLanguage(locale);
}
std::string JSI_L10n::GetLocaleBaseName(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale)
{
return L10n::Instance().GetLocaleBaseName(locale);
}
std::string JSI_L10n::GetLocaleCountry(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale)
{
return L10n::Instance().GetLocaleCountry(locale);
}
std::string JSI_L10n::GetLocaleScript(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale)
{
return L10n::Instance().GetLocaleScript(locale);
}
bool JSI_L10n::ValidateLocale(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale)
{
return L10n::Instance().ValidateLocale(locale);
}
bool JSI_L10n::SaveLocale(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale)
{
return L10n::Instance().SaveLocale(locale);
}
void JSI_L10n::ReevaluateCurrentLocaleAndReload(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
L10n::Instance().ReevaluateCurrentLocaleAndReload();
}
void JSI_L10n::RegisterScriptFunctions(ScriptInterface& scriptInterface)
{
scriptInterface.RegisterFunction<std::wstring, std::wstring, &Translate>("Translate");
scriptInterface.RegisterFunction<std::wstring, std::string, std::wstring, &TranslateWithContext>("TranslateWithContext");
scriptInterface.RegisterFunction<std::wstring, std::wstring, std::wstring, int, &TranslatePlural>("TranslatePlural");
scriptInterface.RegisterFunction<std::wstring, std::string, std::wstring, std::wstring, int, &TranslatePluralWithContext>("TranslatePluralWithContext");
scriptInterface.RegisterFunction<std::wstring, std::wstring, &TranslateLines>("TranslateLines");
scriptInterface.RegisterFunction<std::vector<std::wstring>, std::vector<std::wstring>, &TranslateArray>("TranslateArray");
scriptInterface.RegisterFunction<std::wstring, std::wstring, &MarkToTranslate>("MarkToTranslate");
scriptInterface.RegisterFunction<std::wstring, UDate, std::wstring, &FormatMillisecondsIntoDateString>("FormatMillisecondsIntoDateString");
scriptInterface.RegisterFunction<std::wstring, double, &FormatDecimalNumberIntoString>("FormatDecimalNumberIntoString");
scriptInterface.RegisterFunction<std::vector<std::string>, &GetSupportedLocaleBaseNames>("GetSupportedLocaleBaseNames");
scriptInterface.RegisterFunction<std::vector<std::wstring>, &GetSupportedLocaleDisplayNames>("GetSupportedLocaleDisplayNames");
scriptInterface.RegisterFunction<std::string, &GetCurrentLocale>("GetCurrentLocale");
scriptInterface.RegisterFunction<std::vector<std::string>, &GetAllLocales>("GetAllLocales");
scriptInterface.RegisterFunction<std::string, std::string, &GetDictionaryLocale>("GetDictionaryLocale");
scriptInterface.RegisterFunction<std::vector<std::wstring>, std::string, &GetDictionariesForDictLocale>("GetDictionariesForDictLocale");
scriptInterface.RegisterFunction<std::string, std::string, &GetLocaleLanguage>("GetLocaleLanguage");
scriptInterface.RegisterFunction<std::string, std::string, &GetLocaleBaseName>("GetLocaleBaseName");
scriptInterface.RegisterFunction<std::string, std::string, &GetLocaleCountry>("GetLocaleCountry");
scriptInterface.RegisterFunction<std::string, std::string, &GetLocaleScript>("GetLocaleScript");
scriptInterface.RegisterFunction<bool, std::string, &ValidateLocale>("ValidateLocale");
scriptInterface.RegisterFunction<bool, std::string, &SaveLocale>("SaveLocale");
scriptInterface.RegisterFunction<void, &ReevaluateCurrentLocaleAndReload>("ReevaluateCurrentLocaleAndReload");
}

View File

@ -0,0 +1,55 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_JSINTERFACE_L10N
#define INCLUDED_JSINTERFACE_L10N
#include "scriptinterface/ScriptInterface.h"
#include "lib/external_libraries/icu.h"
namespace JSI_L10n
{
void RegisterScriptFunctions(ScriptInterface& ScriptInterface);
std::wstring Translate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring sourceString);
std::wstring TranslateWithContext(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string context, std::wstring sourceString);
std::wstring TranslatePlural(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring singularSourceString, std::wstring pluralSourceString, int number);
std::wstring TranslatePluralWithContext(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string context, std::wstring singularSourceString, std::wstring pluralSourceString, int number);
std::wstring TranslateLines(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring sourceString);
std::vector<std::wstring> TranslateArray(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::vector<std::wstring> sourceArray);
std::wstring MarkToTranslate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring sourceString);
std::wstring FormatMillisecondsIntoDateString(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), UDate milliseconds, std::wstring formatString);
std::wstring FormatDecimalNumberIntoString(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), double number);
std::vector<std::string> GetSupportedLocaleBaseNames(ScriptInterface::CxPrivate* UNUSED(pCxPrivate));
std::vector<std::wstring> GetSupportedLocaleDisplayNames(ScriptInterface::CxPrivate* UNUSED(pCxPrivate));
std::string GetCurrentLocale(ScriptInterface::CxPrivate* UNUSED(pCxPrivate));
std::vector<std::string> GetAllLocales(ScriptInterface::CxPrivate* UNUSED(pCxPrivate));
std::string GetDictionaryLocale(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string configLocale);
std::vector<std::wstring> GetDictionariesForDictLocale(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale);
std::string GetLocaleLanguage(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale);
std::string GetLocaleBaseName(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale);
std::string GetLocaleCountry(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale);
std::string GetLocaleScript(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale);
bool ValidateLocale(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale);
bool SaveLocale(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string locale);
void ReevaluateCurrentLocaleAndReload(ScriptInterface::CxPrivate* UNUSED(pCxPrivate));
}
#endif

View File

@ -116,6 +116,9 @@ shared_ptr<ScriptRuntime> g_ScriptRuntime;
static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code
bool g_InDevelopmentCopy;
bool g_CheckedIfInDevelopmentCopy = false;
static void SetTextureQuality(int quality)
{
int q_flags;
@ -389,7 +392,7 @@ ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(
return ERI_NOT_IMPLEMENTED;
}
static std::vector<CStr> GetMods(const CmdLineArgs& args, bool dev)
static std::vector<CStr> GetMods(const CmdLineArgs& args)
{
std::vector<CStr> mods = args.GetMultiple("mod");
// List of the mods, to be used by the Gui
@ -401,7 +404,7 @@ static std::vector<CStr> GetMods(const CmdLineArgs& args, bool dev)
// Add the user mod if not explicitly disabled or we have a dev copy so
// that saved files end up in version control and not in the user mod.
if (!dev && !args.Has("noUserMod"))
if (!InDevelopmentCopy() && !args.Has("noUserMod"))
mods.push_back("user");
return mods;
@ -435,9 +438,11 @@ static void InitVfs(const CmdLineArgs& args, int flags)
// (maps, etc) end up in version control.
const OsPath readonlyConfig = paths.RData()/"config"/"";
g_VFS->Mount(L"config/", readonlyConfig);
bool dev = (g_VFS->GetFileInfo(L"config/dev.cfg", NULL) == INFO::OK);
const std::vector<CStr> mods = GetMods(args, dev);
// Engine localization files.
g_VFS->Mount(L"l10n/", paths.RData()/"l10n"/"");
const std::vector<CStr> mods = GetMods(args);
OsPath modPath = paths.RData()/"mods";
OsPath modUserPath = paths.UserData()/"mods";
@ -448,7 +453,7 @@ static void InitVfs(const CmdLineArgs& args, int flags)
size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST;
OsPath modName(mods[i]);
if (dev)
if (InDevelopmentCopy())
{
// We are running a dev copy, so only mount mods in the user mod path
// if the mod does not exist in the data path.
@ -1321,3 +1326,13 @@ void CancelLoad(const CStrW& message)
g_GUI->GetActiveGUI()->GetScriptInterface()->CallFunctionVoid(g_GUI->GetActiveGUI()->GetGlobalObject(), "cancelOnError", message);
}
}
bool InDevelopmentCopy()
{
if (!g_CheckedIfInDevelopmentCopy)
{
g_InDevelopmentCopy = (g_VFS->GetFileInfo(L"config/dev.cfg", NULL) == INFO::OK);
g_CheckedIfInDevelopmentCopy = true;
}
return g_InDevelopmentCopy;
}

View File

@ -67,4 +67,6 @@ extern void InitGraphics(const CmdLineArgs& args, int flags);
extern void Shutdown(int flags);
extern void CancelLoad(const CStrW& message);
extern bool InDevelopmentCopy();
#endif // INCLUDED_GAMESETUP

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -23,6 +23,8 @@
#include "gui/GUIManager.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/file/archive/archive_zip.h"
#include "i18n/L10n.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
@ -121,7 +123,7 @@ Status SavedGames::Save(const std::wstring& name, const std::wstring& descriptio
OsPath realPath;
WARN_RETURN_STATUS_IF_ERR(g_VFS->GetRealPath(filename, realPath));
LOGMESSAGERENDER(L"Saved game to '%ls'\n", realPath.string().c_str());
LOGMESSAGERENDER(wstring_from_utf8(L10n::Instance().Translate("Saved game to '%ls'") + "\n").c_str(), realPath.string().c_str());
return INFO::OK;
}