Fix #139838: UI: Improve languages list and labels

Edit the language list to make it simpler to scan.

- Display languages in a form "Language (Variant)", such as
  "English (US)" instead of "American English" and
  "Portuguese (Brazil)" instead of "Brazilian Portuguese".
  This allows alphabetical sorting by language first.
  This does not apply to endonyms (languages in their own language).
- Use a dash instead of parentheses to separate the endonyms.
- Deduplicate languages (Automatic, American English, British
  English), which all are in English and don't appear in another
  language.

- Remove language categories as headers. They are replaced with
  percentages in the language tooltips. The percentages are
  generated in utils_languages_menu.py and stored in
  locale/languages.

Co-authored-by: Bastien Montagne <bastien@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/140087
This commit is contained in:
Damien Picard
2025-06-11 13:11:40 +02:00
committed by Bastien Montagne
parent 39a138aa92
commit 798f85a710
4 changed files with 89 additions and 101 deletions

View File

@@ -1128,9 +1128,6 @@ def dump_messages(do_messages, do_checks, settings):
for lng in settings.LANGUAGES: for lng in settings.LANGUAGES:
process_msg(msgs, settings.DEFAULT_CONTEXT, lng[1], "Languages labels from bl_i18n_utils/settings.py", process_msg(msgs, settings.DEFAULT_CONTEXT, lng[1], "Languages labels from bl_i18n_utils/settings.py",
reports, None, settings) reports, None, settings)
for cat in settings.LANGUAGES_CATEGORIES:
process_msg(msgs, settings.DEFAULT_CONTEXT, cat[1],
"Language categories labels from bl_i18n_utils/settings.py", reports, None, settings)
# Get strings from asset catalogs and blend files. # Get strings from asset catalogs and blend files.
# This loads each asset blend file in turn. # This loads each asset blend file in turn.

View File

@@ -25,76 +25,70 @@ except ModuleNotFoundError:
############################################################################### ###############################################################################
# The languages defined in Blender. # The languages defined in Blender.
LANGUAGES_CATEGORIES = (
# Min completeness level, UI English label.
(0.95, "Complete"),
(0.33, "In Progress"),
(-1.0, "Starting"),
)
LANGUAGES = ( LANGUAGES = (
# ID, UI English label, ISO code. # ID, UI English label, ISO code.
(0, "Automatic (Automatic)", "DEFAULT"), (0, "Automatic", "DEFAULT"),
(1, "American English (American English)", "en_US"), (1, "English (US)", "en_US"),
(2, "Japanese (日本語)", "ja_JP"), (2, "Japanese - 日本語", "ja_JP"),
(3, "Dutch (Nederlands)", "nl_NL"), (3, "Dutch - Nederlands", "nl_NL"),
(4, "Italian (Italiano)", "it_IT"), (4, "Italian - Italiano", "it_IT"),
(5, "German (Deutsch)", "de_DE"), (5, "German - Deutsch", "de_DE"),
(6, "Finnish (Suomi)", "fi_FI"), (6, "Finnish - Suomi", "fi_FI"),
(7, "Swedish (Svenska)", "sv_SE"), (7, "Swedish - Svenska", "sv_SE"),
(8, "French (Français)", "fr_FR"), (8, "French - Français", "fr_FR"),
(9, "Spanish (Español)", "es"), (9, "Spanish - Español", "es"),
(10, "Catalan (Català)", "ca_AD"), (10, "Catalan - Català", "ca_AD"),
(11, "Czech (Čeština)", "cs_CZ"), (11, "Czech - Čeština", "cs_CZ"),
(12, "Portuguese (Português)", "pt_PT"), (12, "Portuguese (Portugal) - Português europeu", "pt_PT"),
(13, "Simplified Chinese (简体中文)", "zh_HANS"), (13, "Chinese (Simplified) - 简体中文", "zh_HANS"),
(14, "Traditional Chinese (繁體中文)", "zh_HANT"), (14, "Chinese (Traditional) - 繁體中文", "zh_HANT"),
(15, "Russian (Русский)", "ru_RU"), (15, "Russian - Русский", "ru_RU"),
(16, "Croatian (Hrvatski)", "hr_HR"), (16, "Croatian - Hrvatski", "hr_HR"),
(17, "Serbian (Српски)", "sr_RS"), (17, "Serbian (Cyrillic) - Српски", "sr_RS"),
(18, "Ukrainian (Українська)", "uk_UA"), (18, "Ukrainian - Українська", "uk_UA"),
(19, "Polish (Polski)", "pl_PL"), (19, "Polish - Polski", "pl_PL"),
(20, "Romanian (Român)", "ro_RO"), (20, "Romanian - Român", "ro_RO"),
# Using the utf8 flipped form of Arabic (العربية). # Using the utf8 flipped form of Arabic (العربية).
(21, "Arabic (ﺔﻴﺑﺮﻌﻟﺍ)", "ar_EG"), (21, "Arabic - ﺔﻴﺑﺮﻌﻟﺍ", "ar_EG"),
(22, "Bulgarian (Български)", "bg_BG"), (22, "Bulgarian - Български", "bg_BG"),
(23, "Greek (Ελληνικά)", "el_GR"), (23, "Greek - Ελληνικά", "el_GR"),
(24, "Korean (한국어)", "ko_KR"), (24, "Korean - 한국어", "ko_KR"),
(25, "Nepali (नेपाली)", "ne_NP"), (25, "Nepali - नेपाली", "ne_NP"),
# Using the utf8 flipped form of Persian (فارسی). # Using the utf8 flipped form of Persian (فارسی).
(26, "Persian (ﯽﺳﺭﺎﻓ)", "fa_IR"), (26, "Persian - ﯽﺳﺭﺎﻓ", "fa_IR"),
(27, "Indonesian (Bahasa indonesia)", "id_ID"), (27, "Indonesian - Bahasa indonesia", "id_ID"),
(28, "Serbian Latin (Srpski latinica)", "sr_RS@latin"), (28, "Serbian (Latin) - Srpski latinica", "sr_RS@latin"),
(29, "Kyrgyz (Кыргыз тили)", "ky_KG"), (29, "Kyrgyz - Кыргыз тили", "ky_KG"),
(30, "Turkish (Türkçe)", "tr_TR"), (30, "Turkish - Türkçe", "tr_TR"),
(31, "Hungarian (Magyar)", "hu_HU"), (31, "Hungarian - Magyar", "hu_HU"),
(32, "Brazilian Portuguese (Português do Brasil)", "pt_BR"), (32, "Portuguese (Brazil) - Português brasileiro", "pt_BR"),
# Using the utf8 flipped form of Hebrew (עִבְרִית)). # Using the utf8 flipped form of Hebrew (עִבְרִית)).
(33, "Hebrew (תירִבְעִ)", "he_IL"), (33, "Hebrew - תירִבְעִ", "he_IL"),
(34, "Estonian (Eesti keel)", "et_EE"), (34, "Estonian - Eesti keel", "et_EE"),
(35, "Esperanto (Esperanto)", "eo"), (35, "Esperanto - Esperanto", "eo"),
# 36 is free, used to be 'Spanish from Spain' (`es_ES`). # 36 is free, used to be 'Spanish from Spain' (`es_ES`).
(37, "Amharic (አማርኛ)", "am_ET"), (37, "Amharic - አማርኛ", "am_ET"),
(38, "Uzbek (Oʻzbek)", "uz_UZ@latin"), (38, "Uzbek (Latin) - Oʻzbek", "uz_UZ@latin"),
(39, "Uzbek Cyrillic (Ўзбек)", "uz_UZ@cyrillic"), (39, "Uzbek (Cyrillic) - Ўзбек", "uz_UZ@cyrillic"),
(40, "Hindi (हिन्दी)", "hi_IN"), (40, "Hindi - हिन्दी", "hi_IN"),
(41, "Vietnamese (Tiếng Việt)", "vi_VN"), (41, "Vietnamese - Tiếng Việt", "vi_VN"),
(42, "Basque (Euskara)", "eu_EU"), (42, "Basque - Euskara", "eu_EU"),
(43, "Hausa (Hausa)", "ha"), (43, "Hausa - Hausa", "ha"),
(44, "Kazakh (Қазақша)", "kk_KZ"), (44, "Kazakh - Қазақша", "kk_KZ"),
(45, "Abkhaz (Аԥсуа бызшәа)", "ab"), (45, "Abkhaz - Аԥсуа бызшәа", "ab"),
(46, "Thai (ภาษาไทย)", "th_TH"), (46, "Thai - ภาษาไทย", "th_TH"),
(47, "Slovak (Slovenčina)", "sk_SK"), (47, "Slovak - Slovenčina", "sk_SK"),
(48, "Georgian (ქართული)", "ka"), (48, "Georgian - ქართული", "ka"),
(49, "Tamil (தமிழ்)", "ta"), (49, "Tamil - தமிழ்", "ta"),
(50, "Khmer (ខ្មែរ)", "km"), (50, "Khmer - ខ្មែរ", "km"),
(51, "Swahili (Kiswahili)", "sw"), (51, "Swahili - Kiswahili", "sw"),
(52, "Belarusian (беларуску)", "be"), (52, "Belarusian - беларуску", "be"),
(53, "Danish (Dansk)", "da"), (53, "Danish - Dansk", "da"),
(54, "Slovenian (Slovenščina)", "sl"), (54, "Slovenian - Slovenščina", "sl"),
# Using the utf8 flipped form of Urdu (اُردُو). # Using the utf8 flipped form of Urdu (اُردُو).
(55, "Urdu (وُدرُا)", "ur"), (55, "Urdu - وُدرُا", "ur"),
(56, "Lithuanian (Lietuviškai)", "lt"), (56, "Lithuanian - Lietuviškai", "lt"),
(57, "British English (British English)", "en_GB"), (57, "English (UK)", "en_GB"),
) )
# Default context, in py (keep in sync with `BLT_translation.hh`)! # Default context, in py (keep in sync with `BLT_translation.hh`)!

View File

@@ -31,23 +31,22 @@ def gen_menu_file(stats, settings):
else: else:
tmp.append((0.0, uid_num, label, uid, MISSING)) tmp.append((0.0, uid_num, label, uid, MISSING))
stats = tmp stats = tmp
limits = sorted(settings.LANGUAGES_CATEGORIES, key=lambda it: it[0], reverse=True)
idx = 0
stats = sorted(stats, key=lambda it: it[0], reverse=True) stats = sorted(stats, key=lambda it: it[0], reverse=True)
langs_cats = [[] for i in range(len(limits))] langs = []
highest_uid = 0 highest_uid = 0
for lvl, uid_num, label, uid, flag in stats: for lvl, uid_num, label, uid, flag in stats:
if lvl < limits[idx][0]:
# Sub-sort languages by ISO-codes.
langs_cats[idx].sort(key=lambda it: it[2])
idx += 1
if lvl < settings.IMPORT_MIN_LEVEL and flag == OK: if lvl < settings.IMPORT_MIN_LEVEL and flag == OK:
flag = TOOLOW flag = TOOLOW
langs_cats[idx].append((uid_num, label, uid, flag)) if uid_num == 0:
# Insert default language (Automatic) at index 0, after sorting.
default_lang = (uid_num, label, uid, flag, lvl)
continue
langs.append((uid_num, label, uid, flag, lvl))
if abs(uid_num) > highest_uid: if abs(uid_num) > highest_uid:
highest_uid = abs(uid_num) highest_uid = abs(uid_num)
# Sub-sort last group of languages by ISO-codes! # Sort languages by name.
langs_cats[idx].sort(key=lambda it: it[2]) langs.sort(key=lambda it: it[1])
langs.insert(0, default_lang)
data_lines = [ data_lines = [
"# File used by Blender to know which languages (translations) are available, ", "# File used by Blender to know which languages (translations) are available, ",
"# and to generate translation menu.", "# and to generate translation menu.",
@@ -57,22 +56,13 @@ def gen_menu_file(stats, settings):
"# ID must be unique, except for 0 value (marks categories for menu).", "# ID must be unique, except for 0 value (marks categories for menu).",
"# Line starting with a # are comments!", "# Line starting with a # are comments!",
"#", "#",
"# Automatically generated by bl_i18n_utils/update_languages_menu.py script.", "# Automatically generated by bl_i18n_utils/utils_languages_menu.py script.",
"# Highest ID currently in use: {}".format(highest_uid), "# Highest ID currently in use: {}".format(highest_uid),
] ]
for cat, langs_cat in zip(limits, langs_cats): for uid_num, label, uid, flag, lvl in langs:
data_lines.append("#") if flag == OK:
# Write "category menu label"... data_lines.append("{}:{}:{}:{}%".format(uid_num, label, uid, round(lvl * 100)))
if langs_cat:
data_lines.append("0:{}:".format(cat[1]))
else: else:
# Do not write the category if it has no language! # Non-existing, commented entry!
data_lines.append("# Void category! #0:{}:".format(cat[1])) data_lines.append("# {} #{}:{}:{}:{}%".format(FLAG_MESSAGES[flag], uid_num, label, uid, round(lvl * 100)))
# ...and all matching language entries!
for uid_num, label, uid, flag in langs_cat:
if flag == OK:
data_lines.append("{}:{}:{}".format(uid_num, label, uid))
else:
# Non-existing, commented entry!
data_lines.append("# {} #{}:{}:{}".format(FLAG_MESSAGES[flag], uid_num, label, uid))
return data_lines return data_lines

View File

@@ -50,18 +50,18 @@ static int num_locales_menu = 0;
static void free_locales() static void free_locales()
{ {
if (locales) { if (locales_menu) {
int idx = num_locales_menu - 1; /* Last item does not need to be freed! */ int idx = num_locales_menu - 1; /* Last item does not need to be freed! */
while (idx--) { while (idx--) {
MEM_freeN(locales_menu[idx].identifier); MEM_freeN(locales_menu[idx].identifier); /* Also frees locales's relevant value! */
MEM_freeN(locales_menu[idx].name); MEM_freeN(locales_menu[idx].name);
MEM_freeN(locales_menu[idx].description); /* Also frees locales's relevant value! */ MEM_freeN(locales_menu[idx].description);
} }
MEM_freeN(locales);
locales = nullptr;
} }
MEM_SAFE_FREE(locales_menu); MEM_SAFE_FREE(locales_menu);
/* Allocated strings in #locales are shared with #locales_menu[idx].identifier, which are already
* freed above, or are static strings. */
MEM_SAFE_FREE(locales);
num_locales = num_locales_menu = 0; num_locales = num_locales_menu = 0;
} }
@@ -110,7 +110,7 @@ static void fill_locales()
if (num_locales > 0) { if (num_locales > 0) {
locales = MEM_calloc_arrayN<const char *>(num_locales, __func__); locales = MEM_calloc_arrayN<const char *>(num_locales, __func__);
while (line) { while (line) {
const char *loc, *sep1, *sep2, *sep3; const char *loc, *desc, *sep1, *sep2, *sep3;
char *str = (char *)line->link; char *str = (char *)line->link;
if (ELEM(str[0], '#', '\0')) { if (ELEM(str[0], '#', '\0')) {
@@ -133,14 +133,19 @@ static void fill_locales()
if (sep3) { if (sep3) {
locales_menu[idx].identifier = loc = BLI_strdupn(sep2, sep3 - sep2); locales_menu[idx].identifier = loc = BLI_strdupn(sep2, sep3 - sep2);
sep3++;
desc = BLI_sprintfN("Locale code: %s. Translation progress: %s", loc, sep3);
} }
else { else {
locales_menu[idx].identifier = loc = BLI_strdup(sep2); locales_menu[idx].identifier = loc = BLI_strdup(sep2);
desc = BLI_strdup(sep2);
} }
if (id == 0) { if (id == 0) {
/* The DEFAULT/Automatic item... */ /* The DEFAULT/Automatic item... */
if (BLI_strnlen(loc, 2)) { if (BLI_strnlen(loc, 2)) {
MEM_freeN(desc); /* Not used here. */
locales[id] = ""; locales[id] = "";
/* Keep this tip in sync with the one in rna_userdef /* Keep this tip in sync with the one in rna_userdef
* (rna_enum_language_default_items). */ * (rna_enum_language_default_items). */
@@ -148,13 +153,15 @@ static void fill_locales()
"Automatically choose system's defined language " "Automatically choose system's defined language "
"if available, or fall-back to English"); "if available, or fall-back to English");
} }
/* Menu "label", not to be stored in locales! */ /* Menu "label", not to be stored in locales!
* NOTE: Not used since Blender 4.5. */
else { else {
locales_menu[idx].description = BLI_strdup(""); locales_menu[idx].description = desc;
} }
} }
else { else {
locales[id] = locales_menu[idx].description = BLI_strdup(loc); locales[id] = loc;
locales_menu[idx].description = desc;
} }
idx++; idx++;
} }