diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e40cbc00..cb3d919d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated Keycloak to 26.3.2 +- Added Latvian, Portuguese (Brazil), Russian, Ukrainian and Chinese (Taiwan) translation ## [1.4.3](https://github.com/cryptomator/hub/compare/1.4.2...1.4.3) diff --git a/backend/src/main/resources/org/cryptomator/hub/flyway/V20__Support_Regionalized_Translations.sql b/backend/src/main/resources/org/cryptomator/hub/flyway/V20__Support_Regionalized_Translations.sql new file mode 100644 index 000000000..a8353865f --- /dev/null +++ b/backend/src/main/resources/org/cryptomator/hub/flyway/V20__Support_Regionalized_Translations.sql @@ -0,0 +1,9 @@ +ALTER TABLE "user_details" ALTER COLUMN "language" TYPE VARCHAR; + +UPDATE "user_details" SET "language" = 'de-DE' WHERE "language" = 'de'; +UPDATE "user_details" SET "language" = 'en-US' WHERE "language" = 'en'; +UPDATE "user_details" SET "language" = 'fr-FR' WHERE "language" = 'fr'; +UPDATE "user_details" SET "language" = 'it-IT' WHERE "language" = 'it'; +UPDATE "user_details" SET "language" = 'nl-NL' WHERE "language" = 'nl'; +UPDATE "user_details" SET "language" = 'pt-PT' WHERE "language" = 'pt'; +UPDATE "user_details" SET "language" = 'tr-TR' WHERE "language" = 'tr'; diff --git a/frontend/src/components/AdminSettings.vue b/frontend/src/components/AdminSettings.vue index b3526a7a4..fb4430da1 100644 --- a/frontend/src/components/AdminSettings.vue +++ b/frontend/src/components/AdminSettings.vue @@ -276,7 +276,6 @@ import backend, { BillingDto, VersionDto } from '../common/backend'; import config, { absFrontendBaseURL } from '../common/config'; import { FetchUpdateError, LatestVersionDto, updateChecker } from '../common/updatecheck'; import { debounce } from '../common/util'; -import { Locale } from '../i18n/index'; import FetchError from './FetchError.vue'; const { t, d, locale, fallbackLocale } = useI18n({ useScope: 'global' }); @@ -389,10 +388,7 @@ async function fetchData() { function manageSubscription() { const returnUrl = `${absFrontendBaseURL}admin`; - const supportedLanguages = [Locale.EN, Locale.DE]; - const supportedLanguagePathComponents = Object.fromEntries(supportedLanguages.map(lang => [lang, lang == Locale.EN ? '' : `${lang}/`])); - const languagePathComponent = supportedLanguagePathComponents[(locale.value as string).split('-')[0]] ?? supportedLanguagePathComponents[fallbackLocale.value as string] ?? ''; - window.open(`https://cryptomator.org/${languagePathComponent}hub/billing/?hub_id=${admin.value?.hubId}&return_url=${encodeURIComponent(returnUrl)}`, '_self'); + window.open(`https://cryptomator.org/hub/billing/?hub_id=${admin.value?.hubId}&return_url=${encodeURIComponent(returnUrl)}`, '_self'); } async function saveWebOfTrust() { diff --git a/frontend/src/i18n/en-US.json b/frontend/src/i18n/en-US.json index 61aa9e95c..7fe07922b 100644 --- a/frontend/src/i18n/en-US.json +++ b/frontend/src/i18n/en-US.json @@ -1,12 +1,17 @@ { - "locale.en": "English", - "locale.de": "German", - "locale.fr": "French", - "locale.it": "Italian", - "locale.ko": "Korean", - "locale.nl": "Dutch", - "locale.pt": "Portuguese", - "locale.tr": "Turkish", + "locale.en-US": "English", + "locale.de-DE": "German", + "locale.fr-FR": "French", + "locale.it-IT": "Italian", + "locale.ko-KR": "Korean", + "locale.lv-LV": "Latvian", + "locale.nl-NL": "Dutch", + "locale.pt-BR": "Portuguese (Brazil)", + "locale.pt-PT": "Portuguese", + "locale.ru-RU": "Russian", + "locale.tr-TR": "Turkish", + "locale.uk-UA": "Ukrainian", + "locale.zh-TW": "Chinese (Taiwan)", "common.add": "Add", "common.apply": "Apply", diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index d2648b6b0..a93068045 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -1,36 +1,50 @@ import { I18nOptions } from 'vue-i18n'; -import de from './de-DE.json'; -import en from './en-US.json'; -import fr from './fr-FR.json'; -import it from './it-IT.json'; -import ko from './ko-KR.json'; -import nl from './nl-NL.json'; -import pt from './pt-PT.json'; -import tr from './tr-TR.json'; +import deDe from './de-DE.json'; +import enUs from './en-US.json'; +import frFr from './fr-FR.json'; +import itIt from './it-IT.json'; +import koKr from './ko-KR.json'; +import lvLv from './lv-LV.json'; +import nlNl from './nl-NL.json'; +import ptBr from './pt-BR.json'; +import ptPt from './pt-PT.json'; +import ruRu from './ru-RU.json'; +import trTr from './tr-TR.json'; +import uaUa from './uk-UA.json'; +import zhTw from './zh-TW.json'; import { createI18n } from 'vue-i18n'; -// ISO 639‑1 two letter code export enum Locale { - EN = 'en', - DE = 'de', - FR = 'fr', - IT = 'it', - KO = 'ko', - NL = 'nl', - PT = 'pt', - TR = 'tr' + EN_US = 'en-US', + DE_DE = 'de-DE', + FR_FR = 'fr-FR', + IT_IT = 'it-IT', + KO_KR = 'ko-KR', + LV_LV = 'lv-LV', + NL_NL = 'nl-NL', + PT_BR = 'pt-BR', + PT_PT = 'pt-PT', + RU_RU = 'ru-RU', + TR_TR = 'tr-TR', + UK_UA = 'uk-UA', + ZH_TW = 'zh-TW', } export const messages = { - [Locale.EN]: en, - [Locale.DE]: de, - [Locale.FR]: fr, - [Locale.IT]: it, - [Locale.KO]: ko, - [Locale.NL]: nl, - [Locale.PT]: pt, - [Locale.TR]: tr + [Locale.EN_US]: enUs, + [Locale.DE_DE]: deDe, + [Locale.FR_FR]: frFr, + [Locale.IT_IT]: itIt, + [Locale.KO_KR]: koKr, + [Locale.LV_LV]: lvLv, + [Locale.NL_NL]: nlNl, + [Locale.PT_BR]: ptBr, + [Locale.PT_PT]: ptPt, + [Locale.RU_RU]: ruRu, + [Locale.TR_TR]: trTr, + [Locale.UK_UA]: uaUa, + [Locale.ZH_TW]: zhTw }; const defaultShortDatetimeFormat = { @@ -50,14 +64,19 @@ const defaultShortDatetimeFormat = { } as const; export const datetimeFormats: I18nOptions['datetimeFormats'] = { - [Locale.EN]: defaultShortDatetimeFormat, - [Locale.DE]: defaultShortDatetimeFormat, - [Locale.FR]: defaultShortDatetimeFormat, - [Locale.IT]: defaultShortDatetimeFormat, - [Locale.KO]: defaultShortDatetimeFormat, - [Locale.NL]: defaultShortDatetimeFormat, - [Locale.PT]: defaultShortDatetimeFormat, - [Locale.TR]: defaultShortDatetimeFormat + [Locale.EN_US]: defaultShortDatetimeFormat, + [Locale.DE_DE]: defaultShortDatetimeFormat, + [Locale.FR_FR]: defaultShortDatetimeFormat, + [Locale.IT_IT]: defaultShortDatetimeFormat, + [Locale.KO_KR]: defaultShortDatetimeFormat, + [Locale.LV_LV]: defaultShortDatetimeFormat, + [Locale.NL_NL]: defaultShortDatetimeFormat, + [Locale.PT_BR]: defaultShortDatetimeFormat, + [Locale.PT_PT]: defaultShortDatetimeFormat, + [Locale.RU_RU]: defaultShortDatetimeFormat, + [Locale.TR_TR]: defaultShortDatetimeFormat, + [Locale.UK_UA]: defaultShortDatetimeFormat, + [Locale.ZH_TW]: defaultShortDatetimeFormat, }; const defaultNumberFormat = { @@ -68,24 +87,29 @@ const defaultNumberFormat = { } as const; export const numberFormats: I18nOptions['numberFormats'] = { - [Locale.EN]: defaultNumberFormat, - [Locale.DE]: defaultNumberFormat, - [Locale.FR]: defaultNumberFormat, - [Locale.IT]: defaultNumberFormat, - [Locale.KO]: defaultNumberFormat, - [Locale.NL]: defaultNumberFormat, - [Locale.PT]: defaultNumberFormat, - [Locale.TR]: defaultNumberFormat + [Locale.EN_US]: defaultNumberFormat, + [Locale.DE_DE]: defaultNumberFormat, + [Locale.FR_FR]: defaultNumberFormat, + [Locale.IT_IT]: defaultNumberFormat, + [Locale.KO_KR]: defaultNumberFormat, + [Locale.LV_LV]: defaultNumberFormat, + [Locale.NL_NL]: defaultNumberFormat, + [Locale.PT_BR]: defaultNumberFormat, + [Locale.PT_PT]: defaultNumberFormat, + [Locale.RU_RU]: defaultNumberFormat, + [Locale.TR_TR]: defaultNumberFormat, + [Locale.UK_UA]: defaultNumberFormat, + [Locale.ZH_TW]: defaultNumberFormat }; export const mapToLocale = (local: string): Locale => (Object.values(Locale) as string[]).includes(local) ? (local as Locale) - : Locale.EN; + : Locale.EN_US; const i18n = createI18n({ locale: navigator.language, - fallbackLocale: Locale.EN, + fallbackLocale: Locale.EN_US, messages, datetimeFormats, numberFormats,