mirror of
https://github.com/wxWidgets/wxWidgets.git
synced 2026-08-18 01:19:45 +08:00
This commit improves the best locale match algorithm by prioritizing closer matches and add many new tests verifying that this works as expected. In order to do this, new information had to be added to the language database, which was extended with it and updated to use the latest Unicode CLDR data and latest Windows 11 locale list. Further, add a set of scripts for maintaining the language database up to date and a GitHub workflow `genlangdb.yml` which can be run manually to regenerate the wxWidgets language-related source and header files from the underlying Windows and Unicode data. It produces 2 artifacts: - wxLanguageDatabaseDist.zip allows to easily update the language-related files in the wxWidgets repository by simply copying all files. - the normal workflow log and wxLanguageDatabaseLog.zip allow to check all temporary files of the regeneration process and to detect potential issues, before the language-related files are actually replaced by the new ones. Finally, fix wxUILocaleImplName::GetPreferredUILanguages: Under Windows 10 and above the Windows API function ::GetUserPreferredUILanguages() returns only the primary UI language plus US English. Additional preferred UI languages installed by the user are ignored, so instead of using this function read the list of user preferred languages from the Windows registry. Closes #24855.
437 lines
12 KiB
Python
Executable File
437 lines
12 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# Run this script from top-level wxWidgets directory to update the contents of
|
|
# include/wx/intl.h and src/common/intl.cpp using information from langtabl.txt
|
|
#
|
|
# Warning: error detection and reporting here is rudimentary, check if the
|
|
# files were updated correctly with "git diff" before committing them!
|
|
|
|
import os
|
|
import string
|
|
import sys
|
|
|
|
def ReadRegionGroupTable():
|
|
regiongrouptable = []
|
|
try:
|
|
f = open('misc/languages/regiongrouptabl.txt')
|
|
except:
|
|
print("Did you run the script from top-level wxWidgets directory?")
|
|
raise
|
|
|
|
for i in f.readlines():
|
|
ispl = i.split()
|
|
regiongrouptable.append((ispl[0], ispl[1]))
|
|
f.close()
|
|
return regiongrouptable
|
|
|
|
def ReadMatchingTable():
|
|
matchingtable = []
|
|
try:
|
|
f = open('misc/languages/matchingtabl.txt')
|
|
except:
|
|
print("Did you run the script from top-level wxWidgets directory?")
|
|
raise
|
|
|
|
for i in f.readlines():
|
|
ispl = i.split()
|
|
matchingtable.append((ispl[0], ispl[1], ispl[2]))
|
|
f.close()
|
|
return matchingtable
|
|
|
|
def ReadLikelyTable():
|
|
likelytable = []
|
|
try:
|
|
f = open('misc/languages/likelytabl.txt')
|
|
except:
|
|
print("Did you run the script from top-level wxWidgets directory?")
|
|
raise
|
|
|
|
for i in f.readlines():
|
|
ispl = i.split()
|
|
likelytable.append((ispl[0], ispl[1]))
|
|
f.close()
|
|
return likelytable
|
|
|
|
def ReadScriptTable():
|
|
scripttable = []
|
|
try:
|
|
f = open('misc/languages/scripttabl.txt')
|
|
except:
|
|
print("Did you run the script from top-level wxWidgets directory?")
|
|
raise
|
|
|
|
for i in f.readlines():
|
|
ispl = i.split()
|
|
scripttable.append((ispl[0], ispl[1]))
|
|
f.close()
|
|
return scripttable
|
|
|
|
def ReadSynonymTable():
|
|
synonymtable = []
|
|
try:
|
|
f = open('misc/languages/synonymtabl.txt')
|
|
except:
|
|
print("Did you run the script from top-level wxWidgets directory?")
|
|
raise
|
|
|
|
for i in f.readlines():
|
|
ispl = i.split()
|
|
synonymtable.append((ispl[0], ispl[1], ispl[2], ispl[3]))
|
|
f.close()
|
|
return synonymtable
|
|
|
|
def ReadTable():
|
|
table = []
|
|
try:
|
|
f = open('misc/languages/langtabl.txt')
|
|
except:
|
|
print("Did you run the script from top-level wxWidgets directory?")
|
|
raise
|
|
|
|
for i in f.readlines():
|
|
ispl = i.split()
|
|
table.append((ispl[0], ispl[1], ispl[2], ispl[3], ispl[4], ispl[5], ispl[6], ispl[7], ' '.join(ispl[8:])))
|
|
f.close()
|
|
return table
|
|
|
|
|
|
# Kind may be "include" or "interface".
|
|
def WriteEnum(f, table, synonymtable, kind = 'include'):
|
|
f.write("""
|
|
enum wxLanguage
|
|
{
|
|
/// User's default/preferred language as got from OS.
|
|
wxLANGUAGE_DEFAULT,
|
|
|
|
/// Unknown language, returned if wxLocale::GetSystemLanguage fails.
|
|
wxLANGUAGE_UNKNOWN,
|
|
|
|
""");
|
|
knownLangs = []
|
|
output = ''
|
|
for i in table:
|
|
lang = i[0]
|
|
wxver = i[1]
|
|
if lang not in knownLangs:
|
|
output += ' %s,' % lang
|
|
if kind == 'interface' and wxver != '-':
|
|
output += '%s///< @since_wx{%s}' % (' ' * (56 - len(lang)), wxver)
|
|
output += '\n'
|
|
knownLangs.append(lang)
|
|
output += """
|
|
/// For custom, user-defined languages.
|
|
wxLANGUAGE_USER_DEFINED,
|
|
"""
|
|
|
|
if kind == 'include':
|
|
output += '\n /// Synonyms.'
|
|
|
|
output += '\n'
|
|
|
|
for i in synonymtable:
|
|
lang = i[0]
|
|
synonym = i[1]
|
|
wxver = i[3]
|
|
output += ' %s' % lang
|
|
if kind == 'include':
|
|
output += ' = %s,\n' % synonym
|
|
elif kind == 'interface':
|
|
if wxver != '-':
|
|
output += ',%s///< Synonym for %s. @since_wx{%s}\n' % (' ' * (42 - len(lang)), synonym, wxver)
|
|
else:
|
|
output += ',%s///< Synonym for %s.\n' % (' ' * (42 - len(lang)), synonym)
|
|
else:
|
|
print("Unknown kind of generated enum")
|
|
raise
|
|
|
|
output += '};\n\n'
|
|
f.write(output)
|
|
|
|
def WriteTable(f, table, synonymtable):
|
|
f.write("""
|
|
// The following data tables are generated by misc/languages/genlang.py
|
|
// When making changes, please put them into misc/languages/langtabl.txt
|
|
|
|
#if !defined(__WIN32__)
|
|
|
|
#define SETWINLANG(info,lang,sublang)
|
|
|
|
#else
|
|
|
|
#define SETWINLANG(info,lang,sublang) \\
|
|
info.WinLang = tabLangData[j].lang; \\
|
|
info.WinSublang = tabLangData[j].sublang;
|
|
|
|
#endif // __WIN32__
|
|
|
|
// Include actual data tables
|
|
#include "wx/private/lang_info.h"
|
|
#include "wx/private/lang_scripts.h"
|
|
#include "wx/private/lang_likely.h"
|
|
#include "wx/private/lang_match.h"
|
|
#include "wx/private/lang_regions.h"
|
|
|
|
void wxUILocaleImpl::InitLanguagesDB()
|
|
{
|
|
wxLanguageInfo info;
|
|
int j;
|
|
|
|
// Known languages
|
|
for (j = 0; tabLangData[j].wxlang != 0; ++j)
|
|
{
|
|
info.Language = tabLangData[j].wxlang;
|
|
info.LocaleTag = tabLangData[j].bcp47tag;
|
|
info.CanonicalName = tabLangData[j].canonical;
|
|
info.CanonicalRef = tabLangData[j].canonicalref;
|
|
info.LayoutDirection = tabLangData[j].layout;
|
|
info.Description = wxString::FromUTF8(tabLangData[j].desc);
|
|
info.DescriptionNative = wxString::FromUTF8(tabLangData[j].descnative);
|
|
SETWINLANG(info, winlang, winsublang)
|
|
wxUILocale::AddLanguage(info);
|
|
}
|
|
|
|
// Known language scripts
|
|
for (j = 0; tabScriptData[j].scname; ++j)
|
|
{
|
|
gs_scmap_name2alias[tabScriptData[j].scname] = tabScriptData[j].scalias;
|
|
gs_scmap_alias2name[tabScriptData[j].scalias] = tabScriptData[j].scname;
|
|
}
|
|
|
|
// Known likely subtags
|
|
for (j = 0; tabLikelyData[j].tagfrom; ++j)
|
|
{
|
|
gs_likely_subtags_map[tabLikelyData[j].tagfrom] = tabLikelyData[j].tagto;
|
|
}
|
|
|
|
// Known matching language tags
|
|
for (j = 0; tabMatchData[j].matchkey; ++j)
|
|
{
|
|
gs_matching_tags_map[tabMatchData[j].matchkey] = tabMatchData[j].matchval;
|
|
}
|
|
|
|
// Known region groups
|
|
wxString langPrev;
|
|
wxString lang;
|
|
std::unordered_set<wxString>* regionGroup = nullptr;
|
|
for (j = 0; regionGroupData[j].language; ++j)
|
|
{
|
|
lang = regionGroupData[j].language;
|
|
if (!langPrev.IsSameAs(lang))
|
|
{
|
|
langPrev = lang;
|
|
gs_region_groups_map[lang] = std::unordered_set<wxString>();
|
|
regionGroup = &gs_region_groups_map[lang];
|
|
}
|
|
regionGroup->insert(regionGroupData[j].country);
|
|
}
|
|
}
|
|
|
|
""" )
|
|
|
|
def WriteInfoTable(f, table, synonymtable):
|
|
lngtable = ''
|
|
|
|
for i in table:
|
|
ibcp47 = '"%s"' % i[2]
|
|
ican = '"%s"' % i[3]
|
|
if ican == '"-"': ican = '""'
|
|
icanbase = '"%s"' % i[4]
|
|
if icanbase == '"-"': icanbase = '""'
|
|
ilang = i[5]
|
|
if ilang == '-': ilang = '0'
|
|
isublang = i[6]
|
|
if isublang == '-': isublang = '0'
|
|
if (i[7] == "LTR") :
|
|
ilayout = "wxLayout_LeftToRight"
|
|
elif (i[7] == "RTL"):
|
|
ilayout = "wxLayout_RightToLeft"
|
|
else:
|
|
print("ERROR: Invalid value for the layout direction")
|
|
lngtable += ' { %-60s %-17s, %-28s, %-15s, %-4s, %-4s, %s, %s },\n' % \
|
|
((i[0]+','), ibcp47, ican, icanbase, ilang, isublang, ilayout, i[8])
|
|
|
|
f.write("""
|
|
// The following data tables are generated by misc/languages/genlang.py
|
|
// When making changes, please put them into misc/languages/langtabl.txt
|
|
|
|
// Data table for known languages
|
|
static const struct langData_t
|
|
{
|
|
int wxlang;
|
|
const char* bcp47tag;
|
|
const char* canonical;
|
|
const char* canonicalref;
|
|
wxUint32 winlang;
|
|
wxUint32 winsublang;
|
|
wxLayoutDirection layout;
|
|
const char* desc;
|
|
const char* descnative;
|
|
}
|
|
tabLangData[] =
|
|
{
|
|
%s
|
|
{ 0, nullptr, nullptr, nullptr, 0, 0, wxLayout_Default, nullptr, nullptr }
|
|
};
|
|
""" % (lngtable))
|
|
|
|
|
|
def WriteScriptTable(f, scripttable, synonymtable):
|
|
sctable = ''
|
|
for i in scripttable:
|
|
scname = '"%s"' % i[0]
|
|
scalias = '"%s"' % i[1]
|
|
sctable += ' { %s, %s },\n' % (scname, scalias)
|
|
|
|
f.write("""
|
|
// The following data tables are generated by misc/languages/genlang.py
|
|
// When making changes, please put them into misc/languages/scripttabl.txt
|
|
|
|
// Data table for known language scripts
|
|
static const struct scriptData_t
|
|
{
|
|
const char* scname;
|
|
const char* scalias;
|
|
}
|
|
tabScriptData[] =
|
|
{
|
|
%s
|
|
{ nullptr, nullptr }
|
|
};
|
|
""" % (sctable))
|
|
|
|
def WriteLikelyTable(f, likelytable, synonymtable):
|
|
tagtable = ''
|
|
for i in likelytable:
|
|
tagfrom = '"%s"' % i[0]
|
|
tagto = '"%s"' % i[1]
|
|
tagtable += ' { %-11s %s },\n' % (tagfrom+',', tagto)
|
|
|
|
f.write("""
|
|
// The following data tables are generated by misc/languages/genlang.py
|
|
// When making changes, please put them into misc/languages/likelytabl.txt
|
|
|
|
// Data table for likely subtags
|
|
static const struct likelyData_t
|
|
{
|
|
const char* tagfrom;
|
|
const char* tagto;
|
|
}
|
|
tabLikelyData[] =
|
|
{
|
|
%s
|
|
{ nullptr, nullptr }
|
|
};
|
|
""" % (tagtable))
|
|
|
|
def WriteMatchingTable(f, matchingtable, synonymtable):
|
|
matchtable = ''
|
|
for i in matchingtable:
|
|
matchkey = '"%s:%s"' % (i[0], i[1])
|
|
matchval = '%3s' % i[2]
|
|
matchtable += ' { %-20s %s },\n' % (matchkey+',', matchval)
|
|
|
|
f.write("""
|
|
// The following data tables are generated by misc/languages/genlang.py
|
|
// When making changes, please put them into misc/languages/matchingtabl.txt
|
|
|
|
// Data table for matching languages
|
|
static const struct matchData_t
|
|
{
|
|
const char* matchkey;
|
|
int matchval;
|
|
}
|
|
tabMatchData[] =
|
|
{
|
|
%s
|
|
{ nullptr, 0 }
|
|
};
|
|
""" % (matchtable))
|
|
|
|
|
|
def WriteRegionsTable(f, regiongrouptable, synonymtable):
|
|
regiongrptable = ''
|
|
for i in regiongrouptable:
|
|
rg_language = '"%s"' % i[0]
|
|
rg_country = '"%s"' % i[1]
|
|
regiongrptable += ' { %-8s %s },\n' % (rg_language+',', rg_country)
|
|
|
|
f.write("""
|
|
// The following data tables are generated by misc/languages/genlang.py
|
|
// When making changes, please put them into misc/languages/regiongrouptabl.txt
|
|
|
|
// Data table for region groups
|
|
static const struct regionGroupData_t
|
|
{
|
|
const char* language;
|
|
const char* country;
|
|
}
|
|
regionGroupData[] =
|
|
{
|
|
%s
|
|
{ nullptr, nullptr }
|
|
};
|
|
""" % (regiongrptable))
|
|
|
|
|
|
def ReplaceGeneratedPartOfFile(fname, func, table1, table2):
|
|
"""
|
|
Replaces the part of file marked with the special comments with the
|
|
output of func.
|
|
|
|
fname is the name of the input file and func must be a function taking
|
|
a file and language table on input and writing the appropriate chunk to
|
|
this file, e.g. WriteEnum or WriteTable.
|
|
|
|
table1 and table2 are data tables that are passed to func.
|
|
"""
|
|
fin = open(fname, 'rt')
|
|
fnameNew = fname + '.new'
|
|
fout = open(fnameNew, 'wt')
|
|
betweenBeginAndEnd = 0
|
|
afterEnd = 0
|
|
for l in fin.readlines():
|
|
if l == '// --- --- --- generated code begins here --- --- ---\n':
|
|
if betweenBeginAndEnd or afterEnd:
|
|
print('Unexpected starting comment.')
|
|
betweenBeginAndEnd = 1
|
|
fout.write(l)
|
|
func(fout, table1, table2)
|
|
elif l == '// --- --- --- generated code ends here --- --- ---\n':
|
|
if not betweenBeginAndEnd:
|
|
print('End comment found before the starting one?')
|
|
break
|
|
|
|
betweenBeginAndEnd = 0
|
|
afterEnd = 1
|
|
|
|
if not betweenBeginAndEnd:
|
|
fout.write(l)
|
|
|
|
if not afterEnd:
|
|
print('Failed to process %s.' % fname)
|
|
os.remove(fnameNew)
|
|
sys.exit(1)
|
|
|
|
fout.close()
|
|
fin.close()
|
|
os.remove(fname)
|
|
os.rename(fnameNew, fname)
|
|
|
|
|
|
dummytable = []
|
|
table = ReadTable()
|
|
scripttable = ReadScriptTable()
|
|
synonymtable = ReadSynonymTable()
|
|
likelytable = ReadLikelyTable()
|
|
matchingtable = ReadMatchingTable()
|
|
regiongrouptable = ReadRegionGroupTable()
|
|
|
|
ReplaceGeneratedPartOfFile('include/wx/language.h', WriteEnum, table, synonymtable)
|
|
ReplaceGeneratedPartOfFile('interface/wx/language.h', lambda f, table1, table2: WriteEnum(f, table1, table2, 'interface'), table, synonymtable)
|
|
ReplaceGeneratedPartOfFile('src/common/languageinfo.cpp', WriteTable, table, synonymtable)
|
|
ReplaceGeneratedPartOfFile('include/wx/private/lang_info.h', WriteInfoTable, table, synonymtable)
|
|
ReplaceGeneratedPartOfFile('include/wx/private/lang_scripts.h', WriteScriptTable, scripttable, dummytable)
|
|
ReplaceGeneratedPartOfFile('include/wx/private/lang_likely.h', WriteLikelyTable, likelytable, dummytable)
|
|
ReplaceGeneratedPartOfFile('include/wx/private/lang_match.h', WriteMatchingTable, matchingtable, dummytable)
|
|
ReplaceGeneratedPartOfFile('include/wx/private/lang_regions.h', WriteRegionsTable, regiongrouptable, dummytable)
|