mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00

* FIX lang selection resetting to en_US for some langs Fixes https://forums.ankiweb.net/t/anki-25-01-beta/54490/17?u=anon_0000. # Issue Set a hand full of certain languages in the preferences screen and see that the translations have been applied after reboot. The language selection in preferences wrongly shows en_US though, not the current active language. If you wanted to switch to `en_US` in this case, then you'd have to first switch to a working language (like de_DE) and then switch to en_US. # Solution `anki/qt/aqt/preferences.py` has the functions `setup_language()` and `current_lang_index()`. I noticed that it defaults to en_US, if the language is not in `compatMap` and it couldn’t return the index of the current language. No idea if this code is faulty but I headed over to `anki/pylib/anki/lang.py` afterwards. Here, in `compatMap`, I added e.g. `"la": "la_LA"`. I knew the code since I could get it with `print("––– lang is ", lang)` in `preferences.py` (`current_lang_index()` retrieves `la` for latin). After adding those code changes from my PR, the problem for those selected languages had gone away. No idea if that's best practices though or if something else should be fixed instead. * UPDATE CONTRIBUTORS adding myself to the list
253 lines
7.1 KiB
Python
253 lines
7.1 KiB
Python
# Copyright: Ankitects Pty Ltd and contributors
|
||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||
|
||
from __future__ import annotations
|
||
|
||
import locale
|
||
import re
|
||
import warnings
|
||
import weakref
|
||
from typing import TYPE_CHECKING, Any
|
||
|
||
import anki
|
||
import anki._backend
|
||
import anki.i18n_pb2 as _pb
|
||
from anki._legacy import DeprecatedNamesMixinForModule
|
||
|
||
# public exports
|
||
TR = anki._fluent.LegacyTranslationEnum
|
||
FormatTimeSpan = _pb.FormatTimespanRequest
|
||
|
||
|
||
langs = sorted(
|
||
[
|
||
("Afrikaans", "af_ZA"),
|
||
("Bahasa Melayu", "ms_MY"),
|
||
("Català", "ca_ES"),
|
||
("Dansk", "da_DK"),
|
||
("Deutsch", "de_DE"),
|
||
("Eesti", "et_EE"),
|
||
("English (United States)", "en_US"),
|
||
("English (United Kingdom)", "en_GB"),
|
||
("Español", "es_ES"),
|
||
("Esperanto", "eo_UY"),
|
||
("Euskara", "eu_ES"),
|
||
("Français", "fr_FR"),
|
||
("Galego", "gl_ES"),
|
||
("Hrvatski", "hr_HR"),
|
||
("Italiano", "it_IT"),
|
||
("lo jbobau", "jbo_EN"),
|
||
("Lenga d'òc", "oc_FR"),
|
||
("Magyar", "hu_HU"),
|
||
("Nederlands", "nl_NL"),
|
||
("Norsk", "nb_NO"),
|
||
("Polski", "pl_PL"),
|
||
("Português Brasileiro", "pt_BR"),
|
||
("Português", "pt_PT"),
|
||
("Română", "ro_RO"),
|
||
("Slovenčina", "sk_SK"),
|
||
("Slovenščina", "sl_SI"),
|
||
("Suomi", "fi_FI"),
|
||
("Svenska", "sv_SE"),
|
||
("Tiếng Việt", "vi_VN"),
|
||
("Türkçe", "tr_TR"),
|
||
("简体中文", "zh_CN"),
|
||
("日本語", "ja_JP"),
|
||
("繁體中文", "zh_TW"),
|
||
("한국어", "ko_KR"),
|
||
("Čeština", "cs_CZ"),
|
||
("Ελληνικά", "el_GR"),
|
||
("Български", "bg_BG"),
|
||
("Монгол хэл", "mn_MN"),
|
||
("Pусский язык", "ru_RU"),
|
||
("Српски", "sr_SP"),
|
||
("Українська мова", "uk_UA"),
|
||
("Հայերեն", "hy_AM"),
|
||
("עִבְרִית", "he_IL"),
|
||
("العربية", "ar_SA"),
|
||
("فارسی", "fa_IR"),
|
||
("ภาษาไทย", "th_TH"),
|
||
("Latin", "la_LA"),
|
||
("Gaeilge", "ga_IE"),
|
||
("Беларуская мова", "be_BY"),
|
||
("ଓଡ଼ିଆ", "or_OR"),
|
||
("Filipino", "tl"),
|
||
("ئۇيغۇر", "ug"),
|
||
]
|
||
)
|
||
|
||
# compatibility with old versions
|
||
compatMap = {
|
||
"af": "af_ZA",
|
||
"ar": "ar_SA",
|
||
"be": "be_BY",
|
||
"bg": "bg_BG",
|
||
"ca": "ca_ES",
|
||
"cs": "cs_CZ",
|
||
"da": "da_DK",
|
||
"de": "de_DE",
|
||
"el": "el_GR",
|
||
"en": "en_US",
|
||
"eo": "eo_UY",
|
||
"es": "es_ES",
|
||
"et": "et_EE",
|
||
"eu": "eu_ES",
|
||
"fa": "fa_IR",
|
||
"fi": "fi_FI",
|
||
"fr": "fr_FR",
|
||
"gl": "gl_ES",
|
||
"he": "he_IL",
|
||
"hr": "hr_HR",
|
||
"hu": "hu_HU",
|
||
"hy": "hy_AM",
|
||
"it": "it_IT",
|
||
"ja": "ja_JP",
|
||
"jbo": "jbo_EN",
|
||
"ko": "ko_KR",
|
||
"la": "la_LA",
|
||
"mn": "mn_MN",
|
||
"ms": "ms_MY",
|
||
"nl": "nl_NL",
|
||
"nb": "nb_NL",
|
||
"no": "nb_NL",
|
||
"oc": "oc_FR",
|
||
"or": "or_OR",
|
||
"pl": "pl_PL",
|
||
"pt": "pt_PT",
|
||
"ro": "ro_RO",
|
||
"ru": "ru_RU",
|
||
"sk": "sk_SK",
|
||
"sl": "sl_SI",
|
||
"sr": "sr_SP",
|
||
"sv": "sv_SE",
|
||
"th": "th_TH",
|
||
"tr": "tr_TR",
|
||
"uk": "uk_UA",
|
||
"vi": "vi_VN",
|
||
}
|
||
|
||
|
||
def lang_to_disk_lang(lang: str) -> str:
|
||
"""Normalize lang, then convert it to name used on disk."""
|
||
# convert it into our canonical representation first
|
||
lang = lang.replace("-", "_")
|
||
if lang in compatMap:
|
||
lang = compatMap[lang]
|
||
|
||
# these language/region combinations are fully qualified, but with a hyphen
|
||
if lang in (
|
||
"en_GB",
|
||
"ga_IE",
|
||
"hy_AM",
|
||
"nb_NO",
|
||
"nn_NO",
|
||
"pt_BR",
|
||
"pt_PT",
|
||
"sv_SE",
|
||
"zh_CN",
|
||
"zh_TW",
|
||
):
|
||
return lang.replace("_", "-")
|
||
# other languages have the region portion stripped
|
||
match = re.match("(.*)_", lang)
|
||
if match:
|
||
return match.group(1)
|
||
else:
|
||
return lang
|
||
|
||
|
||
# the currently set interface language
|
||
current_lang = "en" # pylint: disable=invalid-name
|
||
|
||
# the current Fluent translation instance. Code in pylib/ should
|
||
# not reference this, and should use col.tr instead. The global
|
||
# instance exists for legacy reasons, and as a convenience for the
|
||
# Qt code.
|
||
current_i18n: anki._backend.RustBackend | None = None # pylint: disable=invalid-name
|
||
tr_legacyglobal = anki._backend.Translations(None)
|
||
|
||
|
||
def _(str: str) -> str:
|
||
print(f"gettext _() is deprecated: {str}")
|
||
return str
|
||
|
||
|
||
def ngettext(single: str, plural: str, num: int) -> str:
|
||
print(f"ngettext() is deprecated: {plural}")
|
||
return plural
|
||
|
||
|
||
def set_lang(lang: str) -> None:
|
||
global current_lang, current_i18n # pylint: disable=invalid-name
|
||
current_lang = lang
|
||
current_i18n = anki._backend.RustBackend(langs=[lang])
|
||
tr_legacyglobal.backend = weakref.ref(current_i18n)
|
||
|
||
|
||
def get_def_lang(user_lang: str | None = None) -> tuple[int, str]:
|
||
"""Return user_lang converted to name used on disk and its index, defaulting to system language
|
||
or English if not available."""
|
||
|
||
def get_index_of_language(wanted_locale: str) -> int | None:
|
||
for i, (_, locale_) in enumerate(langs):
|
||
if locale_ == wanted_locale:
|
||
return i
|
||
return None
|
||
|
||
try:
|
||
# getdefaultlocale() is deprecated since Python 3.11, but we need to keep using it as getlocale() behaves differently: https://bugs.python.org/issue38805
|
||
with warnings.catch_warnings():
|
||
warnings.simplefilter("ignore", DeprecationWarning)
|
||
(sys_lang, enc) = locale.getdefaultlocale()
|
||
except AttributeError:
|
||
# this will return a different format on Windows (e.g. Italian_Italy), resulting in us falling back to en_US
|
||
# further below
|
||
(sys_lang, enc) = locale.getlocale()
|
||
except Exception:
|
||
# fails on osx
|
||
sys_lang = "en_US"
|
||
if user_lang in compatMap:
|
||
user_lang = compatMap[user_lang]
|
||
|
||
idx = None
|
||
lang = None
|
||
for preferred_lang in (user_lang, sys_lang):
|
||
idx = get_index_of_language(preferred_lang)
|
||
is_language_supported = idx is not None
|
||
if is_language_supported:
|
||
assert preferred_lang is not None
|
||
lang = preferred_lang
|
||
break
|
||
# if the specified language and the system language aren't available, revert to english
|
||
is_preferred_language_supported = idx is not None
|
||
if not is_preferred_language_supported:
|
||
lang = "en_US"
|
||
idx = get_index_of_language(lang)
|
||
is_english_supported = idx is not None
|
||
if not is_english_supported:
|
||
raise AssertionError("English is supposed to be a supported language.")
|
||
assert idx is not None and lang is not None
|
||
return (idx, lang)
|
||
|
||
|
||
def is_rtl(lang: str) -> bool:
|
||
return lang in ("he", "ar", "fa", "ug")
|
||
|
||
|
||
# strip off unicode isolation markers from a translated string
|
||
# for testing purposes
|
||
def without_unicode_isolation(string: str) -> str:
|
||
return string.replace("\u2068", "").replace("\u2069", "")
|
||
|
||
|
||
def with_collapsed_whitespace(string: str) -> str:
|
||
return re.sub(r"\s+", " ", string)
|
||
|
||
|
||
_deprecated_names = DeprecatedNamesMixinForModule(globals())
|
||
|
||
|
||
if not TYPE_CHECKING:
|
||
|
||
def __getattr__(name: str) -> Any:
|
||
return _deprecated_names.__getattr__(name)
|