mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-24 07:39:41 +02:00
Feature/auto generate crowdin data (#1071)
* add translated key * update code generation for crowdin generation * use composition api and minor styling changes
This commit is contained in:
parent
022cbd1616
commit
50a67f9301
8 changed files with 391 additions and 175 deletions
|
@ -16,6 +16,7 @@ class CodeTemplates:
|
||||||
class CodeDest:
|
class CodeDest:
|
||||||
interface = CWD / "generated" / "interface.js"
|
interface = CWD / "generated" / "interface.js"
|
||||||
pytest_routes = CWD / "generated" / "test_routes.py"
|
pytest_routes = CWD / "generated" / "test_routes.py"
|
||||||
|
use_locales = PROJECT_DIR / "frontend" / "composables" / "use-locales" / "available-locales.ts"
|
||||||
|
|
||||||
|
|
||||||
class CodeKeys:
|
class CodeKeys:
|
||||||
|
|
|
@ -38,9 +38,9 @@ def generate_global_components_types() -> None:
|
||||||
"layout": PROJECT_DIR / "frontend" / "components" / "Layout",
|
"layout": PROJECT_DIR / "frontend" / "components" / "Layout",
|
||||||
}
|
}
|
||||||
|
|
||||||
def render_template(template: str, data: dict) -> None:
|
def render_template(template: str, data: dict) -> str | None:
|
||||||
template = Template(template)
|
tmpl = Template(template)
|
||||||
return template.render(**data)
|
return tmpl.render(**data)
|
||||||
|
|
||||||
def build_data() -> dict:
|
def build_data() -> dict:
|
||||||
data = {}
|
data = {}
|
||||||
|
@ -54,7 +54,9 @@ def generate_global_components_types() -> None:
|
||||||
destination_file.write_text(text)
|
destination_file.write_text(text)
|
||||||
|
|
||||||
text = render_template(template, build_data())
|
text = render_template(template, build_data())
|
||||||
write_template(text)
|
|
||||||
|
if text:
|
||||||
|
write_template(text)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
@ -63,13 +65,13 @@ def generate_global_components_types() -> None:
|
||||||
|
|
||||||
def generate_typescript_types() -> None:
|
def generate_typescript_types() -> None:
|
||||||
def path_to_module(path: Path):
|
def path_to_module(path: Path):
|
||||||
path: str = str(path)
|
str_path: str = str(path)
|
||||||
|
|
||||||
path = path.removeprefix(str(PROJECT_DIR))
|
str_path = str_path.removeprefix(str(PROJECT_DIR))
|
||||||
path = path.removeprefix("/")
|
str_path = str_path.removeprefix("/")
|
||||||
path = path.replace("/", ".")
|
str_path = str_path.replace("/", ".")
|
||||||
|
|
||||||
return path
|
return str_path
|
||||||
|
|
||||||
schema_path = PROJECT_DIR / "mealie" / "schema"
|
schema_path = PROJECT_DIR / "mealie" / "schema"
|
||||||
types_dir = PROJECT_DIR / "frontend" / "types" / "api-types"
|
types_dir = PROJECT_DIR / "frontend" / "types" / "api-types"
|
||||||
|
@ -94,12 +96,12 @@ def generate_typescript_types() -> None:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
path_as_module = path_to_module(module)
|
path_as_module = path_to_module(module)
|
||||||
generate_typescript_defs(path_as_module, str(out_path), exclude=("CamelModel"))
|
generate_typescript_defs(path_as_module, str(out_path), exclude=("CamelModel")) # type: ignore
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
failed_modules.append(module)
|
failed_modules.append(module)
|
||||||
print("\nModule Errors:", module, "-----------------")
|
print("\nModule Errors:", module, "-----------------") # noqa
|
||||||
print(e) # noqa
|
print(e) # noqa
|
||||||
print("Finished Module Errors:", module, "-----------------\n")
|
print("Finished Module Errors:", module, "-----------------\n") # noqa
|
||||||
|
|
||||||
print("\n📁 Skipped Directories:") # noqa
|
print("\n📁 Skipped Directories:") # noqa
|
||||||
for skipped_dir in skipped_dirs:
|
for skipped_dir in skipped_dirs:
|
||||||
|
|
143
dev/code-generation/gen_locales.py
Normal file
143
dev/code-generation/gen_locales.py
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
import _static
|
||||||
|
import dotenv
|
||||||
|
import requests
|
||||||
|
from fastapi_camelcase import CamelModel
|
||||||
|
from jinja2 import Template
|
||||||
|
from requests import Response
|
||||||
|
from rich import print
|
||||||
|
|
||||||
|
BASE = pathlib.Path(__file__).parent.parent.parent
|
||||||
|
|
||||||
|
API_KEY = dotenv.get_key(BASE / ".env", "CROWDIN_API_KEY")
|
||||||
|
|
||||||
|
NAMES = {
|
||||||
|
"en-US": "American English",
|
||||||
|
"en-GB": "British English",
|
||||||
|
"af-ZA": "Afrikaans (Afrikaans)",
|
||||||
|
"ar-SA": "العربية (Arabic)",
|
||||||
|
"ca-ES": "Català (Catalan)",
|
||||||
|
"cs-CZ": "Čeština (Czech)",
|
||||||
|
"da-DK": "Dansk (Danish)",
|
||||||
|
"de-DE": "Deutsch (German)",
|
||||||
|
"el-GR": "Ελληνικά (Greek)",
|
||||||
|
"es-ES": "Español (Spanish)",
|
||||||
|
"fi-FI": "Suomi (Finnish)",
|
||||||
|
"fr-FR": "Français (French)",
|
||||||
|
"he-IL": "עברית (Hebrew)",
|
||||||
|
"hu-HU": "Magyar (Hungarian)",
|
||||||
|
"it-IT": "Italiano (Italian)",
|
||||||
|
"ja-JP": "日本語 (Japanese)",
|
||||||
|
"ko-KR": "한국어 (Korean)",
|
||||||
|
"no-NO": "Norsk (Norwegian)",
|
||||||
|
"nl-NL": "Nederlands (Dutch)",
|
||||||
|
"pl-PL": "Polski (Polish)",
|
||||||
|
"pt-BR": "Português do Brasil (Brazilian Portuguese)",
|
||||||
|
"pt-PT": "Português (Portugese)",
|
||||||
|
"ro-RO": "Română (Romanian)",
|
||||||
|
"ru-RU": "Pусский (Russian)",
|
||||||
|
"sr-SP": "српски (Serbian)",
|
||||||
|
"sv-SE": "Svenska (Swedish)",
|
||||||
|
"tr-TR": "Türkçe (Turkish)",
|
||||||
|
"uk-UA": "Українська (Ukrainian)",
|
||||||
|
"vi-VN": "Tiếng Việt (Vietnamese)",
|
||||||
|
"zh-CN": "简体中文 (Chinese simplified)",
|
||||||
|
"zh-TW": "繁體中文 (Chinese traditional)",
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCALE_TEMPLATE = """// This Code is auto generated by gen_global_components.py
|
||||||
|
export const LOCALES = [{% for locale in locales %}
|
||||||
|
{
|
||||||
|
name: "{{ locale.name }}",
|
||||||
|
value: "{{ locale.locale }}",
|
||||||
|
progress: {{ locale.progress }},
|
||||||
|
},{% endfor %}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TargetLanguage(CamelModel):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
locale: str
|
||||||
|
threeLettersCode: str
|
||||||
|
twoLettersCode: str
|
||||||
|
progress: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
class CrowdinApi:
|
||||||
|
project_name = "Mealie"
|
||||||
|
project_id = "451976"
|
||||||
|
api_key = API_KEY
|
||||||
|
|
||||||
|
def __init__(self, api_key: str):
|
||||||
|
api_key = api_key
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headers(self) -> dict:
|
||||||
|
return {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_projects(self) -> Response:
|
||||||
|
return requests.get("https://api.crowdin.com/api/v2/projects", headers=self.headers)
|
||||||
|
|
||||||
|
def get_project(self) -> Response:
|
||||||
|
return requests.get(f"https://api.crowdin.com/api/v2/projects/{self.project_id}", headers=self.headers)
|
||||||
|
|
||||||
|
def get_languages(self) -> list[TargetLanguage]:
|
||||||
|
response = self.get_project()
|
||||||
|
tls = response.json()["data"]["targetLanguages"]
|
||||||
|
|
||||||
|
models = [TargetLanguage(**t) for t in tls]
|
||||||
|
|
||||||
|
models.insert(
|
||||||
|
0,
|
||||||
|
TargetLanguage(
|
||||||
|
id="en-US", name="English", locale="en-US", threeLettersCode="en", twoLettersCode="en", progress=100
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
progress: list[dict] = self.get_progress()["data"]
|
||||||
|
|
||||||
|
for model in models:
|
||||||
|
if model.locale in NAMES:
|
||||||
|
model.name = NAMES[model.locale]
|
||||||
|
|
||||||
|
for p in progress:
|
||||||
|
if p["data"]["languageId"] == model.id:
|
||||||
|
model.progress = p["data"]["translationProgress"]
|
||||||
|
|
||||||
|
models.sort(key=lambda x: x.locale, reverse=True)
|
||||||
|
return models
|
||||||
|
|
||||||
|
def get_progress(self) -> dict:
|
||||||
|
response = requests.get(
|
||||||
|
f"https://api.crowdin.com/api/v2/projects/{self.project_id}/languages/progress?limit=500",
|
||||||
|
headers=self.headers,
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Starting...") # noqa
|
||||||
|
|
||||||
|
if API_KEY is None:
|
||||||
|
print("CROWDIN_API_KEY is not set") # noqa
|
||||||
|
return
|
||||||
|
|
||||||
|
api = CrowdinApi("")
|
||||||
|
models = api.get_languages()
|
||||||
|
tmpl = Template(LOCALE_TEMPLATE)
|
||||||
|
rendered = tmpl.render(locales=models)
|
||||||
|
|
||||||
|
with open(_static.CodeDest.use_locales, "w") as f:
|
||||||
|
f.write(rendered) # type:ignore
|
||||||
|
|
||||||
|
print("Finished...") # noqa
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -1,33 +1,46 @@
|
||||||
<template>
|
<template>
|
||||||
<BaseDialog v-model="dialog" :icon="$globals.icons.translate" :title="$t('language-dialog.choose-language')">
|
<BaseDialog v-model="dialog" :icon="$globals.icons.translate" :title="$tc('language-dialog.choose-language')">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
{{ $t('language-dialog.select-description') }}
|
{{ $t("language-dialog.select-description") }}
|
||||||
<v-select
|
<v-autocomplete
|
||||||
v-model="locale"
|
v-model="locale"
|
||||||
:items="locales"
|
:items="locales"
|
||||||
item-text="name"
|
item-text="name"
|
||||||
menu-props="auto"
|
menu-props="auto"
|
||||||
outlined
|
class="my-3"
|
||||||
></v-select>
|
hide-details
|
||||||
<i18n path="language-dialog.how-to-contribute-description">
|
outlined
|
||||||
<template #read-the-docs-link>
|
offset
|
||||||
<a href="https://docs.mealie.io/contributors/translating/" target="_blank">{{ $t("language-dialog.read-the-docs") }}</a>
|
>
|
||||||
</template>
|
<template #item="{ item }">
|
||||||
</i18n>
|
<v-list-item-content>
|
||||||
</v-card-text>
|
<v-list-item-title v-text="item.name"></v-list-item-title>
|
||||||
</BaseDialog>
|
<v-list-item-subtitle> {{ item.progress }}% {{ $tc("language-dialog.translated") }} </v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
</template>
|
||||||
|
</v-autocomplete>
|
||||||
|
<i18n path="language-dialog.how-to-contribute-description">
|
||||||
|
<template #read-the-docs-link>
|
||||||
|
<a href="https://docs.mealie.io/contributors/translating/" target="_blank">{{
|
||||||
|
$t("language-dialog.read-the-docs")
|
||||||
|
}}</a>
|
||||||
|
</template>
|
||||||
|
</i18n>
|
||||||
|
</v-card-text>
|
||||||
|
</BaseDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||||
import type { LocaleObject } from "@nuxtjs/i18n";
|
import type { LocaleObject } from "@nuxtjs/i18n";
|
||||||
|
import { useLocales } from "~/composables/use-locales";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
const dialog = computed<boolean>({
|
const dialog = computed<boolean>({
|
||||||
|
@ -39,145 +52,11 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { i18n } = useContext();
|
const { locales: LOCALES, locale, i18n } = useLocales();
|
||||||
|
|
||||||
const locales = [
|
const locales = LOCALES.filter((locale) =>
|
||||||
{
|
(i18n.locales as LocaleObject[]).map((i18nLocale) => i18nLocale.code).includes(locale.value)
|
||||||
name: "American English",
|
);
|
||||||
value: "en-US",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "British English",
|
|
||||||
value: "en-GB",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Afrikaans (Afrikaans)",
|
|
||||||
value: "af-ZA",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "العربية (Arabic)",
|
|
||||||
value: "ar-SA",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Català (Catalan)",
|
|
||||||
value: "ca-ES",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Čeština (Czech)",
|
|
||||||
value: "cs-CZ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Dansk (Danish)",
|
|
||||||
value: "da-DK",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Deutsch (German)",
|
|
||||||
value: "de-DE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Ελληνικά (Greek)",
|
|
||||||
value: "el-GR",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Español (Spanish)",
|
|
||||||
value: "es-ES",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Suomi (Finnish)",
|
|
||||||
value: "fi-FI",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Français (French)",
|
|
||||||
value: "fr-FR",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "עברית (Hebrew)",
|
|
||||||
value: "he-IL",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Magyar (Hungarian)",
|
|
||||||
value: "hu-HU",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Italiano (Italian)",
|
|
||||||
value: "it-IT",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "日本語 (Japanese)",
|
|
||||||
value: "ja-JP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "한국어 (Korean)",
|
|
||||||
value: "ko-KR",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Norsk (Norwegian)",
|
|
||||||
value: "no-NO",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nederlands (Dutch)",
|
|
||||||
value: "nl-NL",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Polski (Polish)",
|
|
||||||
value: "pl-PL",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Português do Brasil (Brazilian Portugese)",
|
|
||||||
value: "pt-BR",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Português (Portugese)",
|
|
||||||
value: "pt-PT",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Română (Romanian)",
|
|
||||||
value: "ro-RO",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Pусский (Russian)",
|
|
||||||
value: "ru-RU",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "српски (Serbian)",
|
|
||||||
value: "sr-SP",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Svenska (Swedish)",
|
|
||||||
value: "sv-SE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Türkçe (Turkish)",
|
|
||||||
value: "tr-TR",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Українська (Ukrainian)",
|
|
||||||
value: "uk-UA",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Tiếng Việt (Vietnamese)",
|
|
||||||
value: "vi-VN",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "简体中文 (Chinese simplified)",
|
|
||||||
value: "zh-CN",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "繁體中文 (Chinese traditional)",
|
|
||||||
value: "zh-TW",
|
|
||||||
},
|
|
||||||
].filter(locale => (i18n.locales as LocaleObject[]).map(i18nLocale => i18nLocale.code).includes(locale.value));
|
|
||||||
|
|
||||||
const locale = computed<string>({
|
|
||||||
get() {
|
|
||||||
return i18n.locale;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
i18n.setLocale(value);
|
|
||||||
// Reload the page to update the language - not all strings are reactive
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dialog,
|
dialog,
|
||||||
|
@ -185,10 +64,8 @@ export default defineComponent({
|
||||||
locales,
|
locales,
|
||||||
locale,
|
locale,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
168
frontend/composables/use-locales/available-locales.ts
Normal file
168
frontend/composables/use-locales/available-locales.ts
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
// This Code is auto generated by gen_global_components.py
|
||||||
|
export const LOCALES = [
|
||||||
|
{
|
||||||
|
name: "繁體中文 (Chinese traditional)",
|
||||||
|
value: "zh-TW",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "简体中文 (Chinese simplified)",
|
||||||
|
value: "zh-CN",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Tiếng Việt (Vietnamese)",
|
||||||
|
value: "vi-VN",
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Українська (Ukrainian)",
|
||||||
|
value: "uk-UA",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Türkçe (Turkish)",
|
||||||
|
value: "tr-TR",
|
||||||
|
progress: 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Svenska (Swedish)",
|
||||||
|
value: "sv-SE",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "српски (Serbian)",
|
||||||
|
value: "sr-SP",
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Slovak",
|
||||||
|
value: "sk-SK",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pусский (Russian)",
|
||||||
|
value: "ru-RU",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Română (Romanian)",
|
||||||
|
value: "ro-RO",
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Português (Portugese)",
|
||||||
|
value: "pt-PT",
|
||||||
|
progress: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Português do Brasil (Brazilian Portuguese)",
|
||||||
|
value: "pt-BR",
|
||||||
|
progress: 64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Polski (Polish)",
|
||||||
|
value: "pl-PL",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Norsk (Norwegian)",
|
||||||
|
value: "no-NO",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Nederlands (Dutch)",
|
||||||
|
value: "nl-NL",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "한국어 (Korean)",
|
||||||
|
value: "ko-KR",
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "日本語 (Japanese)",
|
||||||
|
value: "ja-JP",
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Italiano (Italian)",
|
||||||
|
value: "it-IT",
|
||||||
|
progress: 99,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Magyar (Hungarian)",
|
||||||
|
value: "hu-HU",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "עברית (Hebrew)",
|
||||||
|
value: "he-IL",
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Français (French)",
|
||||||
|
value: "fr-FR",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "French, Canada",
|
||||||
|
value: "fr-CA",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Suomi (Finnish)",
|
||||||
|
value: "fi-FI",
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Español (Spanish)",
|
||||||
|
value: "es-ES",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "American English",
|
||||||
|
value: "en-US",
|
||||||
|
progress: 100.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "British English",
|
||||||
|
value: "en-GB",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ελληνικά (Greek)",
|
||||||
|
value: "el-GR",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Deutsch (German)",
|
||||||
|
value: "de-DE",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dansk (Danish)",
|
||||||
|
value: "da-DK",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Čeština (Czech)",
|
||||||
|
value: "cs-CZ",
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Català (Catalan)",
|
||||||
|
value: "ca-ES",
|
||||||
|
progress: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "العربية (Arabic)",
|
||||||
|
value: "ar-SA",
|
||||||
|
progress: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Afrikaans (Afrikaans)",
|
||||||
|
value: "af-ZA",
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
];
|
1
frontend/composables/use-locales/index.ts
Normal file
1
frontend/composables/use-locales/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { useLocales } from "./use-locales";
|
23
frontend/composables/use-locales/use-locales.ts
Normal file
23
frontend/composables/use-locales/use-locales.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { computed, useContext } from "@nuxtjs/composition-api";
|
||||||
|
import { LOCALES } from "./available-locales";
|
||||||
|
|
||||||
|
export const useLocales = () => {
|
||||||
|
const { i18n } = useContext();
|
||||||
|
|
||||||
|
const locale = computed<string>({
|
||||||
|
get() {
|
||||||
|
return i18n.locale;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
i18n.setLocale(value);
|
||||||
|
// Reload the page to update the language - not all strings are reactive
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
locales: LOCALES,
|
||||||
|
i18n,
|
||||||
|
};
|
||||||
|
};
|
|
@ -497,7 +497,8 @@
|
||||||
"you-are-not-allowed-to-delete-this-user": "You are not allowed to delete this user"
|
"you-are-not-allowed-to-delete-this-user": "You are not allowed to delete this user"
|
||||||
},
|
},
|
||||||
"language-dialog": {
|
"language-dialog": {
|
||||||
"choose-language": "Choose language",
|
"translated": "translated",
|
||||||
|
"choose-language": "Choose Language",
|
||||||
"select-description": "Choose the language for the Mealie UI. The setting only applies to you, not other users.",
|
"select-description": "Choose the language for the Mealie UI. The setting only applies to you, not other users.",
|
||||||
"how-to-contribute-description": "Is something not translated yet, mistranslated, or your language missing from the list? {read-the-docs-link} on how to contribute!",
|
"how-to-contribute-description": "Is something not translated yet, mistranslated, or your language missing from the list? {read-the-docs-link} on how to contribute!",
|
||||||
"read-the-docs": "Read the docs"
|
"read-the-docs": "Read the docs"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue