Merge pull request #112092 from timothyqiu/ts-nums

Move localized number formatting methods to `TranslationServer`
This commit is contained in:
Thaddeus Crews 2025-11-10 17:47:09 -06:00
commit 8327dfa215
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
27 changed files with 348 additions and 394 deletions

View file

@ -1232,3 +1232,32 @@ static const char *plural_rules[][2] = {
{ "cy", "nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n==3 ? 3 : n==6 ? 4 : 5);" },
{ nullptr, nullptr }
};
static const struct NumSystemData {
const char *locales;
const char32_t *digits;
const char32_t *percent_sign;
const char32_t *exp_l;
const char32_t *exp_u;
} num_system_data[] = {
// Eastern Arabic numerals.
{ "ar ar_AE ar_BH ar_DJ ar_EG ar_ER ar_IL ar_IQ ar_JO ar_KM ar_KW ar_LB ar_MR ar_OM ar_PS ar_QA ar_SA ar_SD ar_SO ar_SS ar_SY ar_TD ar_YE ckb ckb_IQ ckb_IR sd sd_PK sd_Arab sd_Arab_PK", U"٠١٢٣٤٥٦٧٨٩٫", U"٪", U"اس", U"اس" },
// Persian and Urdu numerals.
{ "fa fa_AF fa_IR ks ks_IN ks_Arab ks_Arab_IN lrc lrc_IQ lrc_IR mzn mzn_IR pa_PK pa_Arab pa_Arab_PK ps ps_AF ps_PK ur_IN uz_AF uz_Arab uz_Arab_AF", U"۰۱۲۳۴۵۶۷۸۹٫", U"٪", U"اس", U"اس" },
// Bengali numerals.
{ "as as_IN bn bn_BD bn_IN mni mni_IN mni_Beng mni_Beng_IN", U"০১২৩৪৫৬৭৮৯.", U"%", U"e", U"E" },
// Devanagari numerals.
{ "mr mr_IN ne ne_IN ne_NP sa sa_IN", U"०१२३४५६७८९.", U"%", U"e", U"E" },
// Dzongkha numerals.
{ "dz dz_BT", U"༠༡༢༣༤༥༦༧༨༩.", U"%", U"e", U"E" },
// Santali numerals.
{ "sat sat_IN sat_Olck sat_Olck_IN", U"᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙.", U"%", U"e", U"E" },
// Burmese numerals.
{ "my my_MM", U"၀၁၂၃၄၅၆၇၈၉.", U"%", U"e", U"E" },
// Chakma numerals.
{ "ccp ccp_BD ccp_IN", U"𑄶𑄷𑄸𑄹𑄺𑄻𑄼𑄽𑄾𑄿.", U"%", U"e", U"E" },
// Adlam numerals.
{ "ff ff_Adlm_BF ff_Adlm_CM ff_Adlm_GH ff_Adlm_GM ff_Adlm_GN ff_Adlm_GW ff_Adlm_LR ff_Adlm_MR ff_Adlm_NE ff_Adlm_NG ff_Adlm_SL ff_Adlm_SN", U"𞥐𞥑𞥒𞥓𞥔𞥕𞥖𞥗𞥘𞥙.", U"%", U"𞤉", U"𞤉" },
// End sentinel.
{ nullptr, nullptr, nullptr, nullptr, nullptr }
};

View file

@ -117,6 +117,26 @@ void TranslationServer::init_locale_info() {
}
idx++;
}
// Init number systems.
num_system_map.clear();
idx = 0;
while (num_system_data[idx].locales != nullptr) {
const NumSystemData &nsd = num_system_data[idx];
// These fields must not be empty.
DEV_ASSERT(nsd.percent_sign && nsd.percent_sign[0] != '\0');
DEV_ASSERT(nsd.digits && nsd.digits[0] != '\0');
DEV_ASSERT(nsd.exp_l && nsd.exp_l[0] != '\0');
DEV_ASSERT(nsd.exp_u && nsd.exp_u[0] != '\0');
DEV_ASSERT(strlen(nsd.digits) == 11);
const Vector<String> locales = String(nsd.locales).split(" ");
for (const String &l : locales) {
num_system_map[l] = idx;
}
idx++;
}
}
TranslationServer::Locale::operator String() const {
@ -219,6 +239,66 @@ TranslationServer::Locale::Locale(const TranslationServer &p_server, const Strin
}
}
String TranslationServer::format_number(const String &p_string, const String &p_locale) const {
ERR_FAIL_COND_V(p_locale.is_empty(), p_string);
if (!num_system_map.has(p_locale)) {
return p_string;
}
int index = num_system_map[p_locale];
const NumSystemData &nsd = num_system_data[index];
String res = p_string;
res = res.replace("e", nsd.exp_l);
res = res.replace("E", nsd.exp_u);
char32_t *data = res.ptrw();
for (int j = 0; j < res.length(); j++) {
if (data[j] >= 0x30 && data[j] <= 0x39) {
data[j] = nsd.digits[data[j] - 0x30];
} else if (data[j] == '.' || data[j] == ',') {
data[j] = nsd.digits[10];
}
}
return res;
}
String TranslationServer::parse_number(const String &p_string, const String &p_locale) const {
ERR_FAIL_COND_V(p_locale.is_empty(), p_string);
if (!num_system_map.has(p_locale)) {
return p_string;
}
int index = num_system_map[p_locale];
const NumSystemData &nsd = num_system_data[index];
String res = p_string;
res = res.replace(nsd.exp_l, "e");
res = res.replace(nsd.exp_u, "E");
char32_t *data = res.ptrw();
for (int j = 0; j < res.length(); j++) {
if (data[j] == nsd.digits[10]) {
data[j] = '.';
} else {
for (int k = 0; k < 10; k++) {
if (data[j] == nsd.digits[k]) {
data[j] = 0x30 + k;
}
}
}
}
return res;
}
String TranslationServer::get_percent_sign(const String &p_locale) const {
ERR_FAIL_COND_V(p_locale.is_empty(), "%");
if (!num_system_map.has(p_locale)) {
return "%";
}
int index = num_system_map[p_locale];
return num_system_data[index].percent_sign;
}
String TranslationServer::standardize_locale(const String &p_locale, bool p_add_defaults) const {
return Locale(*this, p_locale, p_add_defaults).operator String();
}
@ -604,6 +684,10 @@ void TranslationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
ClassDB::bind_method(D_METHOD("format_number", "number", "locale"), &TranslationServer::format_number);
ClassDB::bind_method(D_METHOD("get_percent_sign", "locale"), &TranslationServer::get_percent_sign);
ClassDB::bind_method(D_METHOD("parse_number", "number", "locale"), &TranslationServer::parse_number);
ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);

View file

@ -91,6 +91,7 @@ class TranslationServer : public Object {
static inline HashMap<String, String> country_rename_map;
static inline HashMap<String, String> variant_map;
static inline HashMap<String, String> plural_rules_map;
static inline HashMap<String, int> num_system_map;
void init_locale_info();
@ -137,6 +138,10 @@ public:
void set_pseudolocalization_enabled(bool p_enabled);
void reload_pseudolocalization();
String format_number(const String &p_string, const String &p_locale) const;
String parse_number(const String &p_string, const String &p_locale) const;
String get_percent_sign(const String &p_locale) const;
String standardize_locale(const String &p_locale, bool p_add_defaults = false) const;
int compare_locales(const String &p_locale_a, const String &p_locale_b) const;