feat: RTL Support for RTL Languages (Hebrew, Arabic) (#2832)

* add language direction to locale generation

* apply language direction when setting language

---------

Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
This commit is contained in:
Michael Genson 2024-01-19 10:56:36 -06:00 committed by GitHub
parent d17e46ee50
commit 10ba4d2d7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 110 additions and 37 deletions

View File

@ -1,4 +1,5 @@
import pathlib import pathlib
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
import dotenv import dotenv
@ -15,38 +16,44 @@ BASE = pathlib.Path(__file__).parent.parent.parent
API_KEY = dotenv.get_key(BASE / ".env", "CROWDIN_API_KEY") API_KEY = dotenv.get_key(BASE / ".env", "CROWDIN_API_KEY")
NAMES = { @dataclass
"en-US": "American English", class LocaleData:
"en-GB": "British English", name: str
"af-ZA": "Afrikaans (Afrikaans)", dir: str = "ltr"
"ar-SA": "العربية (Arabic)",
"ca-ES": "Català (Catalan)",
"cs-CZ": "Čeština (Czech)", LOCALE_DATA: dict[str, LocaleData] = {
"da-DK": "Dansk (Danish)", "en-US": LocaleData(name="American English"),
"de-DE": "Deutsch (German)", "en-GB": LocaleData(name="British English"),
"el-GR": "Ελληνικά (Greek)", "af-ZA": LocaleData(name="Afrikaans (Afrikaans)"),
"es-ES": "Español (Spanish)", "ar-SA": LocaleData(name="العربية (Arabic)", dir="rtl"),
"fi-FI": "Suomi (Finnish)", "ca-ES": LocaleData(name="Català (Catalan)"),
"fr-FR": "Français (French)", "cs-CZ": LocaleData(name="Čeština (Czech)"),
"he-IL": "עברית (Hebrew)", "da-DK": LocaleData(name="Dansk (Danish)"),
"hu-HU": "Magyar (Hungarian)", "de-DE": LocaleData(name="Deutsch (German)"),
"it-IT": "Italiano (Italian)", "el-GR": LocaleData(name="Ελληνικά (Greek)"),
"ja-JP": "日本語 (Japanese)", "es-ES": LocaleData(name="Español (Spanish)"),
"ko-KR": "한국어 (Korean)", "fi-FI": LocaleData(name="Suomi (Finnish)"),
"no-NO": "Norsk (Norwegian)", "fr-FR": LocaleData(name="Français (French)"),
"nl-NL": "Nederlands (Dutch)", "he-IL": LocaleData(name="עברית (Hebrew)", dir="rtl"),
"pl-PL": "Polski (Polish)", "hu-HU": LocaleData(name="Magyar (Hungarian)"),
"pt-BR": "Português do Brasil (Brazilian Portuguese)", "it-IT": LocaleData(name="Italiano (Italian)"),
"pt-PT": "Português (Portuguese)", "ja-JP": LocaleData(name="日本語 (Japanese)"),
"ro-RO": "Română (Romanian)", "ko-KR": LocaleData(name="한국어 (Korean)"),
"ru-RU": "Pусский (Russian)", "no-NO": LocaleData(name="Norsk (Norwegian)"),
"sr-SP": "српски (Serbian)", "nl-NL": LocaleData(name="Nederlands (Dutch)"),
"sv-SE": "Svenska (Swedish)", "pl-PL": LocaleData(name="Polski (Polish)"),
"tr-TR": "Türkçe (Turkish)", "pt-BR": LocaleData(name="Português do Brasil (Brazilian Portuguese)"),
"uk-UA": "Українська (Ukrainian)", "pt-PT": LocaleData(name="Português (Portuguese)"),
"vi-VN": "Tiếng Việt (Vietnamese)", "ro-RO": LocaleData(name="Română (Romanian)"),
"zh-CN": "简体中文 (Chinese simplified)", "ru-RU": LocaleData(name="Pусский (Russian)"),
"zh-TW": "繁體中文 (Chinese traditional)", "sr-SP": LocaleData(name="српски (Serbian)"),
"sv-SE": LocaleData(name="Svenska (Swedish)"),
"tr-TR": LocaleData(name="Türkçe (Turkish)"),
"uk-UA": LocaleData(name="Українська (Ukrainian)"),
"vi-VN": LocaleData(name="Tiếng Việt (Vietnamese)"),
"zh-CN": LocaleData(name="简体中文 (Chinese simplified)"),
"zh-TW": LocaleData(name="繁體中文 (Chinese traditional)"),
} }
LOCALE_TEMPLATE = """// This Code is auto generated by gen_global_components.py LOCALE_TEMPLATE = """// This Code is auto generated by gen_global_components.py
@ -55,6 +62,7 @@ export const LOCALES = [{% for locale in locales %}
name: "{{ locale.name }}", name: "{{ locale.name }}",
value: "{{ locale.locale }}", value: "{{ locale.locale }}",
progress: {{ locale.progress }}, progress: {{ locale.progress }},
dir: "{{ locale.dir }}",
},{% endfor %} },{% endfor %}
] ]
@ -65,6 +73,7 @@ class TargetLanguage(MealieModel):
id: str id: str
name: str name: str
locale: str locale: str
dir: str
threeLettersCode: str threeLettersCode: str
twoLettersCode: str twoLettersCode: str
progress: float = 0.0 progress: float = 0.0
@ -103,15 +112,23 @@ class CrowdinApi:
models.insert( models.insert(
0, 0,
TargetLanguage( TargetLanguage(
id="en-US", name="English", locale="en-US", threeLettersCode="en", twoLettersCode="en", progress=100 id="en-US",
name="English",
locale="en-US",
dir="ltr",
threeLettersCode="en",
twoLettersCode="en",
progress=100,
), ),
) )
progress: list[dict] = self.get_progress()["data"] progress: list[dict] = self.get_progress()["data"]
for model in models: for model in models:
if model.locale in NAMES: if model.locale in LOCALE_DATA:
model.name = NAMES[model.locale] locale_data = LOCALE_DATA[model.locale]
model.name = locale_data.name
model.dir = locale_data.dir
for p in progress: for p in progress:
if p["data"]["languageId"] == model.id: if p["data"]["languageId"] == model.id:

View File

@ -4,195 +4,234 @@ export const LOCALES = [
name: "繁體中文 (Chinese traditional)", name: "繁體中文 (Chinese traditional)",
value: "zh-TW", value: "zh-TW",
progress: 28, progress: 28,
dir: "ltr",
}, },
{ {
name: "简体中文 (Chinese simplified)", name: "简体中文 (Chinese simplified)",
value: "zh-CN", value: "zh-CN",
progress: 65, progress: 65,
dir: "ltr",
}, },
{ {
name: "Tiếng Việt (Vietnamese)", name: "Tiếng Việt (Vietnamese)",
value: "vi-VN", value: "vi-VN",
progress: 2, progress: 2,
dir: "ltr",
}, },
{ {
name: "Українська (Ukrainian)", name: "Українська (Ukrainian)",
value: "uk-UA", value: "uk-UA",
progress: 99, progress: 99,
dir: "ltr",
}, },
{ {
name: "Türkçe (Turkish)", name: "Türkçe (Turkish)",
value: "tr-TR", value: "tr-TR",
progress: 50, progress: 50,
dir: "ltr",
}, },
{ {
name: "Svenska (Swedish)", name: "Svenska (Swedish)",
value: "sv-SE", value: "sv-SE",
progress: 71, progress: 71,
dir: "ltr",
}, },
{ {
name: "српски (Serbian)", name: "српски (Serbian)",
value: "sr-SP", value: "sr-SP",
progress: 4, progress: 4,
dir: "ltr",
}, },
{ {
name: "Slovenian", name: "Slovenian",
value: "sl-SI", value: "sl-SI",
progress: 49, progress: 49,
dir: "ltr",
}, },
{ {
name: "Slovak", name: "Slovak",
value: "sk-SK", value: "sk-SK",
progress: 97, progress: 97,
dir: "ltr",
}, },
{ {
name: "Pусский (Russian)", name: "Pусский (Russian)",
value: "ru-RU", value: "ru-RU",
progress: 99, progress: 99,
dir: "ltr",
}, },
{ {
name: "Română (Romanian)", name: "Română (Romanian)",
value: "ro-RO", value: "ro-RO",
progress: 32, progress: 32,
dir: "ltr",
}, },
{ {
name: "Português (Portuguese)", name: "Português (Portuguese)",
value: "pt-PT", value: "pt-PT",
progress: 99, progress: 99,
dir: "ltr",
}, },
{ {
name: "Português do Brasil (Brazilian Portuguese)", name: "Português do Brasil (Brazilian Portuguese)",
value: "pt-BR", value: "pt-BR",
progress: 98, progress: 98,
dir: "ltr",
}, },
{ {
name: "Polski (Polish)", name: "Polski (Polish)",
value: "pl-PL", value: "pl-PL",
progress: 97, progress: 97,
dir: "ltr",
}, },
{ {
name: "Norsk (Norwegian)", name: "Norsk (Norwegian)",
value: "no-NO", value: "no-NO",
progress: 85, progress: 85,
dir: "ltr",
}, },
{ {
name: "Nederlands (Dutch)", name: "Nederlands (Dutch)",
value: "nl-NL", value: "nl-NL",
progress: 98, progress: 98,
dir: "ltr",
}, },
{ {
name: "Latvian", name: "Latvian",
value: "lv-LV", value: "lv-LV",
progress: 1, progress: 1,
dir: "ltr",
}, },
{ {
name: "Lithuanian", name: "Lithuanian",
value: "lt-LT", value: "lt-LT",
progress: 97, progress: 97,
dir: "ltr",
}, },
{ {
name: "한국어 (Korean)", name: "한국어 (Korean)",
value: "ko-KR", value: "ko-KR",
progress: 5, progress: 5,
dir: "ltr",
}, },
{ {
name: "日本語 (Japanese)", name: "日本語 (Japanese)",
value: "ja-JP", value: "ja-JP",
progress: 11, progress: 11,
dir: "ltr",
}, },
{ {
name: "Italiano (Italian)", name: "Italiano (Italian)",
value: "it-IT", value: "it-IT",
progress: 96, progress: 96,
dir: "ltr",
}, },
{ {
name: "Magyar (Hungarian)", name: "Magyar (Hungarian)",
value: "hu-HU", value: "hu-HU",
progress: 99, progress: 99,
dir: "ltr",
}, },
{ {
name: "Croatian", name: "Croatian",
value: "hr-HR", value: "hr-HR",
progress: 97, progress: 97,
dir: "ltr",
}, },
{ {
name: "עברית (Hebrew)", name: "עברית (Hebrew)",
value: "he-IL", value: "he-IL",
progress: 99, progress: 99,
dir: "rtl",
}, },
{ {
name: "Galician", name: "Galician",
value: "gl-ES", value: "gl-ES",
progress: 1, progress: 1,
dir: "ltr",
}, },
{ {
name: "Français (French)", name: "Français (French)",
value: "fr-FR", value: "fr-FR",
progress: 99, progress: 99,
dir: "ltr",
}, },
{ {
name: "French, Canada", name: "French, Canada",
value: "fr-CA", value: "fr-CA",
progress: 97, progress: 97,
dir: "ltr",
}, },
{ {
name: "Suomi (Finnish)", name: "Suomi (Finnish)",
value: "fi-FI", value: "fi-FI",
progress: 95, progress: 95,
dir: "ltr",
}, },
{ {
name: "Español (Spanish)", name: "Español (Spanish)",
value: "es-ES", value: "es-ES",
progress: 76, progress: 76,
dir: "ltr",
}, },
{ {
name: "American English", name: "American English",
value: "en-US", value: "en-US",
progress: 100.0, progress: 100.0,
dir: "ltr",
}, },
{ {
name: "British English", name: "British English",
value: "en-GB", value: "en-GB",
progress: 4, progress: 4,
dir: "ltr",
}, },
{ {
name: "Ελληνικά (Greek)", name: "Ελληνικά (Greek)",
value: "el-GR", value: "el-GR",
progress: 35, progress: 35,
dir: "ltr",
}, },
{ {
name: "Deutsch (German)", name: "Deutsch (German)",
value: "de-DE", value: "de-DE",
progress: 99, progress: 99,
dir: "ltr",
}, },
{ {
name: "Dansk (Danish)", name: "Dansk (Danish)",
value: "da-DK", value: "da-DK",
progress: 100, progress: 100,
dir: "ltr",
}, },
{ {
name: "Čeština (Czech)", name: "Čeština (Czech)",
value: "cs-CZ", value: "cs-CZ",
progress: 66, progress: 66,
dir: "ltr",
}, },
{ {
name: "Català (Catalan)", name: "Català (Catalan)",
value: "ca-ES", value: "ca-ES",
progress: 61, progress: 61,
dir: "ltr",
}, },
{ {
name: "Bulgarian", name: "Bulgarian",
value: "bg-BG", value: "bg-BG",
progress: 99, progress: 99,
dir: "ltr",
}, },
{ {
name: "العربية (Arabic)", name: "العربية (Arabic)",
value: "ar-SA", value: "ar-SA",
progress: 16, progress: 16,
dir: "rtl",
}, },
{ {
name: "Afrikaans (Afrikaans)", name: "Afrikaans (Afrikaans)",
value: "af-ZA", value: "af-ZA",
progress: 96, progress: 96,
dir: "ltr",
}, },
] ]

View File

@ -4,14 +4,31 @@ import { LOCALES } from "./available-locales";
export const useLocales = () => { export const useLocales = () => {
const { i18n, $vuetify } = useContext(); const { i18n, $vuetify } = useContext();
function getLocale(value: string) {
const currentLocale = LOCALES.filter((locale) => locale.value === value);
return currentLocale.length ? currentLocale[0] : null;
}
const locale = computed<string>({ const locale = computed<string>({
get() { get() {
$vuetify.lang.current = i18n.locale; // dirty hack // dirty hack
$vuetify.lang.current = i18n.locale;
const currentLocale = getLocale(i18n.locale);
if (currentLocale) {
$vuetify.rtl = currentLocale.dir === "rtl";
}
return i18n.locale; return i18n.locale;
}, },
set(value) { set(value) {
i18n.setLocale(value); i18n.setLocale(value);
$vuetify.lang.current = value; // this does not persist after window reload :-(
// this does not persist after window reload :-(
$vuetify.lang.current = value;
const currentLocale = getLocale(value);
if (currentLocale) {
$vuetify.rtl = currentLocale.dir === "rtl";
}
// Reload the page to update the language - not all strings are reactive // Reload the page to update the language - not all strings are reactive
window.location.reload(); window.location.reload();