ts: add i18n typing

This commit is contained in:
axolotle 2024-07-11 15:10:18 +02:00
parent 8bb2451e9c
commit 00fe9f42bc
3 changed files with 45 additions and 27 deletions

View file

@ -1,40 +1,42 @@
import { nextTick } from 'vue'
import store from '@/store'
import i18n from '@/i18n' import i18n from '@/i18n'
import supportedLocales from './supportedLocales' import store from '@/store'
import { nextTick } from 'vue'
export let dateFnsLocale import supportedLocales, {
isSupportedLocale,
type SupportedLocales,
} from '@/i18n/supportedLocales'
export let dateFnsLocale: any
/** /**
* Returns the first two supported locales that can be found in the `localStorage` or * Returns the first two supported locales that can be found in the `localStorage` or
* in the user browser settings. * in the user browser settings.
*
* @return {string[]}
*/ */
function getDefaultLocales() { function getDefaultLocales() {
const locale = store.getters.locale const locale: SupportedLocales | null = store.getters.locale
const fallbackLocale = store.getters.fallbackLocale const fallbackLocale: SupportedLocales | null = store.getters.fallbackLocale
if (locale && fallbackLocale) return [locale, fallbackLocale] if (locale && fallbackLocale) return [locale, fallbackLocale]
const navigatorLocales = navigator.languages || [navigator.language] const navigatorLocales = navigator.languages || [navigator.language]
const defaultLocales = [] const defaultLocales: SupportedLocales[] = []
const supported = Object.keys(supportedLocales)
for (const locale of navigatorLocales) { for (const locale of navigatorLocales) {
if (supported.includes(locale) && !defaultLocales.includes(locale)) { if (isSupportedLocale(locale) && !defaultLocales.includes(locale)) {
defaultLocales.push(locale) defaultLocales.push(locale)
} else { } else {
const lang = locale.split('-')[0] const lang = locale.split('-')[0]
if (supported.includes(lang) && !defaultLocales.includes(lang)) { if (isSupportedLocale(lang) && !defaultLocales.includes(lang)) {
defaultLocales.push(lang) defaultLocales.push(lang)
} }
} }
if (defaultLocales.length === 2) break if (defaultLocales.length === 2) break
} }
return defaultLocales return defaultLocales as [SupportedLocales, SupportedLocales]
} }
export async function setI18nLocale(locale) { export async function setI18nLocale(locale: SupportedLocales) {
if (!i18n.global.availableLocales.includes(locale)) { if (!i18n.global.availableLocales.includes(locale)) {
await loadLocaleMessages(locale) await loadLocaleMessages(locale)
// also query/set the date-fns locale object for time translation // also query/set the date-fns locale object for time translation
@ -47,22 +49,24 @@ export async function setI18nLocale(locale) {
} }
if (i18n.mode === 'legacy') { if (i18n.mode === 'legacy') {
// @ts-ignore
i18n.global.locale = locale i18n.global.locale = locale
} else { } else {
i18n.global.locale.value = locale i18n.global.locale.value = locale
} }
document.querySelector('html').setAttribute('lang', locale) document.querySelector('html')!.setAttribute('lang', locale)
// FIXME can't currently change document direction easily since bootstrap still doesn't handle rtl. // FIXME can't currently change document direction easily since bootstrap still doesn't handle rtl.
// document.dir = locale === 'ar' ? 'rtl' : 'ltr' // document.dir = locale === 'ar' ? 'rtl' : 'ltr'
} }
export async function setI18nFallbackLocale(locale) { export async function setI18nFallbackLocale(locale: SupportedLocales) {
if (!i18n.global.availableLocales.includes(locale)) { if (!i18n.global.availableLocales.includes(locale)) {
await loadLocaleMessages(locale) await loadLocaleMessages(locale)
} }
if (i18n.mode === 'legacy') { if (i18n.mode === 'legacy') {
// @ts-ignore
i18n.global.fallbackLocale = [locale, 'en'] i18n.global.fallbackLocale = [locale, 'en']
} else { } else {
i18n.global.fallbackLocale.value = [locale, 'en'] i18n.global.fallbackLocale.value = [locale, 'en']
@ -74,7 +78,7 @@ export async function setI18nFallbackLocale(locale) {
* *
* @return {Promise<string>} Promise that resolve the given locale string * @return {Promise<string>} Promise that resolve the given locale string
*/ */
export async function loadLocaleMessages(locale) { export async function loadLocaleMessages(locale: SupportedLocales) {
// load locale messages with dynamic import // load locale messages with dynamic import
const messages = await import(`./locales/${locale}.json`) const messages = await import(`./locales/${locale}.json`)
@ -87,8 +91,8 @@ export async function loadLocaleMessages(locale) {
/** /**
* Loads a date-fns locale object * Loads a date-fns locale object
*/ */
async function loadDateFnsLocale(locale) { async function loadDateFnsLocale(locale: SupportedLocales) {
const dateFnsLocaleName = supportedLocales[locale].dateFnsLocale || locale const dateFnsLocaleName = supportedLocales[locale].dateFnsLocale ?? locale
dateFnsLocale = ( dateFnsLocale = (
await import(`../../node_modules/date-fns/locale/${dateFnsLocaleName}.mjs`) await import(`../../node_modules/date-fns/locale/${dateFnsLocaleName}.mjs`)
).default ).default

View file

@ -4,7 +4,7 @@
// If a new locale or a new date-fns locale is added, add it to the supported // If a new locale or a new date-fns locale is added, add it to the supported
// locales list in `app/vue.config.js` // locales list in `app/vue.config.js`
export default { const supportedLocales = {
ar: { ar: {
name: 'عربي', name: 'عربي',
}, },
@ -137,4 +137,21 @@ export default {
name: '简化字', name: '简化字',
dateFnsLocale: 'zh-CN', dateFnsLocale: 'zh-CN',
}, },
} as const
type SL = typeof supportedLocales
export type SupportedLocales = keyof SL
export type SupportedDateFnsLocales = keyof {
[k in SupportedLocales as SL[k] extends { dateFnsLocale: string }
? SL[k]['dateFnsLocale']
: k]: never
} }
export function isSupportedLocale(locale: string): locale is SupportedLocales {
return Object.keys(supportedLocales).includes(locale)
}
export default supportedLocales as Record<
SupportedLocales,
{ name: string; dateFnsLocale?: SupportedDateFnsLocales }
>

View file

@ -15,7 +15,6 @@ export default {
dark: localStorage.getItem('dark') === 'true', dark: localStorage.getItem('dark') === 'true',
experimental: localStorage.getItem('experimental') === 'true', experimental: localStorage.getItem('experimental') === 'true',
spinner: 'pacman', spinner: 'pacman',
supportedLocales,
}, },
mutations: { mutations: {
@ -85,12 +84,10 @@ export default {
experimental: (state) => state.experimental, experimental: (state) => state.experimental,
spinner: (state) => state.spinner, spinner: (state) => state.spinner,
availableLocales: (state) => { availableLocales: () => {
return Object.entries(state.supportedLocales).map( return Object.entries(supportedLocales).map(([locale, { name }]) => {
([locale, { name }]) => { return { value: locale, text: name }
return { value: locale, text: name } })
},
)
}, },
}, },
} }