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:
process_msg(msgs, settings.DEFAULT_CONTEXT, lng[1], "Languages labels from bl_i18n_utils/settings.py",
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.
# This loads each asset blend file in turn.

View File

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

View File

@@ -31,23 +31,22 @@ def gen_menu_file(stats, settings):
else:
tmp.append((0.0, uid_num, label, uid, MISSING))
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)
langs_cats = [[] for i in range(len(limits))]
langs = []
highest_uid = 0
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:
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:
highest_uid = abs(uid_num)
# Sub-sort last group of languages by ISO-codes!
langs_cats[idx].sort(key=lambda it: it[2])
# Sort languages by name.
langs.sort(key=lambda it: it[1])
langs.insert(0, default_lang)
data_lines = [
"# File used by Blender to know which languages (translations) are available, ",
"# 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).",
"# 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),
]
for cat, langs_cat in zip(limits, langs_cats):
data_lines.append("#")
# Write "category menu label"...
if langs_cat:
data_lines.append("0:{}:".format(cat[1]))
for uid_num, label, uid, flag, lvl in langs:
if flag == OK:
data_lines.append("{}:{}:{}:{}%".format(uid_num, label, uid, round(lvl * 100)))
else:
# Do not write the category if it has no language!
data_lines.append("# Void category! #0:{}:".format(cat[1]))
# ...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))
# Non-existing, commented entry!
data_lines.append("# {} #{}:{}:{}:{}%".format(FLAG_MESSAGES[flag], uid_num, label, uid, round(lvl * 100)))
return data_lines

View File

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