Skip to content

Commit d70ee07

Browse files
committed
Show percent translation complete for language options
1 parent 3dea523 commit d70ee07

5 files changed

Lines changed: 143 additions & 13 deletions

File tree

data/meta/Lang.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,10 @@ function Lang.GetResource(name, langCode) return Resource end
2626
---@return string[]
2727
function Lang.GetAvailableLanguages(resource) end
2828

29+
--- Return the percentage of translated strings for a language code (0-100).
30+
--- English always returns 100.
31+
---@param langCode string
32+
---@return integer
33+
function Lang.GetLanguageCompletionPercent(langCode) end
34+
2935
return Lang

data/pigui/modules/settings-window.lua

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,14 +365,40 @@ end
365365
local function showLanguageOptions()
366366
local langs = Lang.GetAvailableLanguages("core")
367367

368+
local filtered = {}
369+
for _, lang in ipairs(langs) do
370+
if lang == "en" or lang == Lang.currentLanguage or Lang.GetLanguageCompletionPercent(lang) > 0 then
371+
table.insert(filtered, lang)
372+
end
373+
end
374+
langs = filtered
375+
376+
table.sort(langs, function(a, b)
377+
if a == "en" then return true end
378+
if b == "en" then return false end
379+
return a < b
380+
end)
381+
382+
local function languageLabel(lang)
383+
local name = Lang.GetResource("core", lang).LANG_NAME
384+
if lang == "en" then
385+
return name
386+
end
387+
local pct = Lang.GetLanguageCompletionPercent(lang)
388+
if pct >= 90 then
389+
return name
390+
end
391+
return string.format("%s (%d%%)", name, pct)
392+
end
393+
368394
ui.withFont(pionillium.heading, function()
369395
ui.text(lui.LANGUAGE_RESTART_GAME_TO_APPLY .. ":")
370396
end)
371397

372398
ui.withFont(pionillium.body, function()
373399
ui.child("##LanguageList", Vector2(0, 0), function()
374400
for _, lang in ipairs(langs) do
375-
if ui.selectable(Lang.GetResource("core",lang).LANG_NAME .. "##" .. lang, Lang.currentLanguage==lang, {}) then
401+
if ui.selectable(languageLabel(lang) .. "##" .. lang, Lang.currentLanguage==lang, {}) then
376402
Lang.SetCurrentLanguage(lang)
377403
end
378404
end

src/Lang.cpp

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,8 @@ namespace Lang {
3333
return true;
3434
}
3535

36-
bool Resource::Load()
36+
static void load_messages_from_json(const Json &data, const std::string &filename, Resource::StringMap &strings)
3737
{
38-
if (m_loaded)
39-
return true;
40-
41-
std::string filename = "lang/" + m_name + "/" + m_langCode + ".json";
42-
Json data = JsonUtils::LoadJsonDataFile(filename);
43-
if (data.is_null()) {
44-
Log::Warning("couldn't read language file '{}'\n", filename.c_str());
45-
return false;
46-
}
47-
4838
for (Json::iterator i = data.begin(); i != data.end(); ++i) {
4939
const std::string token = i.key();
5040
if (token.empty()) {
@@ -97,7 +87,28 @@ namespace Lang {
9787
text = adjustedText;
9888
}
9989

100-
m_strings[token] = text;
90+
strings[token] = text;
91+
}
92+
}
93+
94+
static bool load_messages_from_file(const std::string &filename, Resource::StringMap &strings)
95+
{
96+
Json data = JsonUtils::LoadJsonDataFile(filename);
97+
if (data.is_null())
98+
return false;
99+
load_messages_from_json(data, filename, strings);
100+
return true;
101+
}
102+
103+
bool Resource::Load()
104+
{
105+
if (m_loaded)
106+
return true;
107+
108+
std::string filename = "lang/" + m_name + "/" + m_langCode + ".json";
109+
if (!load_messages_from_file(filename, m_strings)) {
110+
Log::Warning("couldn't read language file '{}'\n", filename.c_str());
111+
return false;
101112
}
102113

103114
m_loaded = true;
@@ -232,4 +243,73 @@ namespace Lang {
232243
return m_cachedResources.at(key);
233244
}
234245

246+
static std::map<std::string, int> s_languageCompletion;
247+
static bool s_languageCompletionScanned = false;
248+
249+
void ScanLanguageCompletion()
250+
{
251+
if (s_languageCompletionScanned)
252+
return;
253+
s_languageCompletionScanned = true;
254+
255+
struct Stats {
256+
int translated = 0;
257+
int total = 0;
258+
};
259+
std::map<std::string, Stats> stats;
260+
261+
const int dirFlags = FileSystem::FileEnumerator::IncludeDirs | FileSystem::FileEnumerator::ExcludeFiles;
262+
for (FileSystem::FileEnumerator resources(FileSystem::gameDataFiles, "lang", dirFlags); !resources.Finished(); resources.Next()) {
263+
const std::string resourcePath = resources.Current().GetPath();
264+
265+
Resource::StringMap english;
266+
if (!load_messages_from_file(FileSystem::JoinPath(resourcePath, "en.json"), english) || english.empty())
267+
continue;
268+
269+
for (FileSystem::FileEnumerator files(FileSystem::gameDataFiles, resourcePath); !files.Finished(); files.Next()) {
270+
if (!files.Current().IsFile())
271+
continue;
272+
273+
const std::string &name = files.Current().GetName();
274+
if (!ends_with_ci(name, ".json"))
275+
continue;
276+
277+
const std::string langCode = name.substr(0, name.size() - 5);
278+
if (langCode == "en")
279+
continue;
280+
281+
Resource::StringMap translated;
282+
load_messages_from_file(files.Current().GetPath(), translated);
283+
284+
for (const auto &entry : english) {
285+
const std::string &token = entry.first;
286+
const std::string &enText = entry.second;
287+
stats[langCode].total++;
288+
const auto it = translated.find(token);
289+
if (it != translated.end() && it->second != enText)
290+
stats[langCode].translated++;
291+
}
292+
}
293+
}
294+
295+
for (const auto &entry : stats) {
296+
const Stats &s = entry.second;
297+
s_languageCompletion[entry.first] = s.total > 0 ? (s.translated * 100) / s.total : 0;
298+
}
299+
}
300+
301+
int GetLanguageCompletionPercent(std::string_view langCode)
302+
{
303+
if (langCode == "en")
304+
return 100;
305+
306+
if (!s_languageCompletionScanned)
307+
ScanLanguageCompletion();
308+
309+
const auto it = s_languageCompletion.find(std::string(langCode));
310+
if (it == s_languageCompletion.end())
311+
return 0;
312+
return it->second;
313+
}
314+
235315
} // namespace Lang

src/Lang.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ namespace Lang {
5555

5656
Resource &GetResource(std::string_view name, std::string_view langCode);
5757

58+
// Scan all language files under lang/ and compute the percentage of strings
59+
// that differ from the English version for each language code.
60+
void ScanLanguageCompletion();
61+
62+
// Return the completion percentage (0-100) for a language code.
63+
// Returns 100 for English. Computes completion on first call if needed.
64+
int GetLanguageCompletionPercent(std::string_view langCode);
65+
5866
} // namespace Lang
5967

6068
#endif

src/lua/LuaLang.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,15 @@ static int l_lang_get_available_languages(lua_State *l)
124124
return 1;
125125
}
126126

127+
static int l_lang_get_language_completion_percent(lua_State *l)
128+
{
129+
LUA_DEBUG_START(l);
130+
const std::string langCode(luaL_checkstring(l, 1));
131+
lua_pushinteger(l, Lang::GetLanguageCompletionPercent(langCode));
132+
LUA_DEBUG_END(l, 1);
133+
return 1;
134+
}
135+
127136
static int l_lang_set_current_language(lua_State *l)
128137
{
129138
const std::vector<std::string> langs = Lang::Resource::GetAvailableLanguages("core");
@@ -158,6 +167,7 @@ void LuaLang::Register()
158167
static const luaL_Reg l_methods[] = {
159168
{ "GetResource", l_lang_get_resource },
160169
{ "GetAvailableLanguages", l_lang_get_available_languages },
170+
{ "GetLanguageCompletionPercent", l_lang_get_language_completion_percent },
161171
{ "SetCurrentLanguage", l_lang_set_current_language },
162172
{ 0, 0 }
163173
};

0 commit comments

Comments
 (0)