151 lines
4.2 KiB
TypeScript
151 lines
4.2 KiB
TypeScript
import * as R from "fp-ts/Record"
|
|
import * as A from "fp-ts/Array"
|
|
import * as O from "fp-ts/Option"
|
|
import { pipe } from "fp-ts/function"
|
|
import { createI18n, I18n, I18nOptions } from "vue-i18n"
|
|
import { HoppModule } from "."
|
|
|
|
import languages from "../../languages.json"
|
|
|
|
import { throwError } from "~/helpers/functional/error"
|
|
import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence"
|
|
|
|
/*
|
|
In context of this file, we have 2 main kinds of things.
|
|
1. Locale -> A locale is termed as the i18n entries present in the /locales folder
|
|
2. Language -> A language is an entry in the /languages.json folder
|
|
|
|
Each language entry should correspond to a locale entry.
|
|
*/
|
|
|
|
/*
|
|
* As we migrate out of Nuxt I18n into our own system for i18n management,
|
|
* Some stuff has changed regarding how it works.
|
|
*
|
|
* The previous system works by using paths to represent locales to load.
|
|
* Basically, /es/realtime will load the /realtime page but with 'es' language
|
|
*
|
|
* In the new system instead of relying on the lang code, we store the language
|
|
* in the application local config store (localStorage). The URLs don't have
|
|
* a locale path effect
|
|
*/
|
|
|
|
// TODO: Syncing into settings ?
|
|
|
|
const LOCALES = import.meta.glob("../../locales/*.json")
|
|
|
|
type LanguagesDef = {
|
|
code: string
|
|
file: string
|
|
iso: string
|
|
name: string
|
|
dir?: "ltr" | "rtl" // Text Orientation (defaults to 'ltr')
|
|
}
|
|
|
|
const FALLBACK_LANG_CODE = "en"
|
|
|
|
// TypeScript cannot understand dir is restricted to "ltr" or "rtl" yet, hence assertion
|
|
export const APP_LANGUAGES: LanguagesDef[] = languages as LanguagesDef[]
|
|
|
|
export const APP_LANG_CODES = languages.map(({ code }) => code)
|
|
|
|
export const FALLBACK_LANG = pipe(
|
|
APP_LANGUAGES,
|
|
A.findFirst((x) => x.code === FALLBACK_LANG_CODE),
|
|
O.getOrElseW(() =>
|
|
throwError(`Could not find the fallback language '${FALLBACK_LANG_CODE}'`)
|
|
)
|
|
)
|
|
|
|
// A reference to the i18n instance
|
|
let i18nInstance: I18n<any, any, any> | null = null
|
|
|
|
const resolveCurrentLocale = () =>
|
|
pipe(
|
|
// Resolve from locale and make sure it is in languages
|
|
getLocalConfig("locale"),
|
|
O.fromNullable,
|
|
O.filter((locale) =>
|
|
pipe(
|
|
APP_LANGUAGES,
|
|
A.some(({ code }) => code === locale)
|
|
)
|
|
),
|
|
|
|
// Else load from navigator.language
|
|
O.alt(() =>
|
|
pipe(
|
|
APP_LANGUAGES,
|
|
A.findFirst(({ code }) => navigator.language.startsWith(code)), // en-US should also match to en
|
|
O.map(({ code }) => code)
|
|
)
|
|
),
|
|
|
|
// Else load fallback
|
|
O.getOrElse(() => FALLBACK_LANG_CODE)
|
|
)
|
|
|
|
/**
|
|
* Changes the application language. This function returns a promise as
|
|
* the locale files are lazy loaded on demand
|
|
* @param locale The locale code of the language to load
|
|
*/
|
|
export const changeAppLanguage = async (locale: string) => {
|
|
const localeData = (
|
|
(await pipe(
|
|
LOCALES,
|
|
R.lookup(`../../locales/${locale}.json`),
|
|
O.getOrElseW(() =>
|
|
throwError(
|
|
`Tried to change app language to non-existent locale '${locale}'`
|
|
)
|
|
)
|
|
)()) as any
|
|
).default
|
|
|
|
if (!i18nInstance) {
|
|
throw new Error("Tried to change language without active i18n instance")
|
|
}
|
|
|
|
i18nInstance.global.setLocaleMessage(locale, localeData)
|
|
|
|
// TODO: Look into the type issues here
|
|
i18nInstance.global.locale.value = locale
|
|
|
|
setLocalConfig("locale", locale)
|
|
}
|
|
|
|
export default <HoppModule>{
|
|
onVueAppInit(app) {
|
|
const i18n = createI18n(<I18nOptions>{
|
|
locale: "en", // TODO: i18n system!
|
|
fallbackLocale: "en",
|
|
legacy: false,
|
|
allowComposition: true,
|
|
})
|
|
|
|
app.use(i18n)
|
|
|
|
i18nInstance = i18n
|
|
|
|
// TODO: Global loading state to hide the resolved lang loading
|
|
const currentLocale = resolveCurrentLocale()
|
|
changeAppLanguage(currentLocale)
|
|
|
|
setLocalConfig("locale", currentLocale)
|
|
},
|
|
onBeforeRouteChange(to, _, router) {
|
|
// Convert old locale path format to new format
|
|
const oldLocalePathLangCode = APP_LANG_CODES.find((langCode) =>
|
|
to.path.startsWith(`/${langCode}/`)
|
|
)
|
|
|
|
// Change language to the correct lang code
|
|
if (oldLocalePathLangCode) {
|
|
changeAppLanguage(oldLocalePathLangCode)
|
|
|
|
router.replace(to.path.substring(`/${oldLocalePathLangCode}`.length))
|
|
}
|
|
},
|
|
}
|