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:
parent
30d5b572a6
commit
d6db5a466d
@ -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")
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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
486
source/i18n/L10n.cpp
Normal 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
95
source/i18n/L10n.h
Normal 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
|
178
source/i18n/scripting/JSInterface_L10n.cpp
Normal file
178
source/i18n/scripting/JSInterface_L10n.cpp
Normal 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");
|
||||
}
|
55
source/i18n/scripting/JSInterface_L10n.h
Normal file
55
source/i18n/scripting/JSInterface_L10n.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user