Experimental features
Automated translation
Translation automation will involve following steps:
- Gathering of missing translations - using i18n Missing Handler and write in local storage
- Batch send missing translations to backend
- Process (translate with AI)
- Lazy load from back end processed translations as user navigates
Check i18n API of odb4bb.
Implementation
- Add new api methods at
@/api/index.ts
ts
// ...
export type I18nData = {
module: string
locale: string
key: string
value: string
}
// ...
export const appApi = {
// ...
async getI18n(module: string, locale: string): Promise<HttpResponse<{ [key: string]: string }>> {
return await http.get('i18n/', { module, locale })
},
async setI18n(i18n: string): Promise<HttpResponse<void>> {
return await http.post('i18n_batch/', { i18n })
},
}
- Organize all features, a new store is created
@/stores/app/i18n.ts
ts
import { defineStore, acceptHMRUpdate } from 'pinia'
import { appApi, type I18nData } from '@/api'
export const useI18nStore = defineStore('i18n', () => {
let initialTranslations: I18nData[] = []
try {
const stored = localStorage.getItem('i18n')
if (stored) initialTranslations = JSON.parse(stored)
} catch {
initialTranslations = []
}
const translations = ref<I18nData[]>(initialTranslations)
watch(
() => translations.value,
(newData) => {
try {
localStorage.setItem('i18n', JSON.stringify(newData))
} finally {
// do nothing
}
},
{ deep: true },
)
const i18n = useI18n()
const addTranslation = (locale: string, key: string) => {
if (process.env.NODE_ENV === 'production') return
try {
const route = useRoute()
const module = route ? route.path.split('/')[1] : ''
if (
!translations.value.some(
(t) => (t.module === module || t.module === '') && t.locale === locale && t.key === key,
)
) {
const value = key
.split('.')
.join(' ')
.replace(/^./, (c) => c.toUpperCase())
translations.value.push({ module, locale, key, value })
console.log(`[i18n] Translation added`, module, locale, key, value)
}
} catch (error) {
console.warn(`[i18n] Failed to add translation`, locale, error)
}
}
const loadedTranslations = new Set<string>()
async function syncTranslations() {
if (!translations) return
const { error } = await appApi.setI18n(JSON.stringify(translations.value))
if (error) console.warn('[i18n] Failed to sync translations', error.message)
else {
translations.value = []
console.log('[i18n] Synced translations', translations.value)
}
}
async function loadTranslations(path: string) {
if (process.env.NODE_ENV !== 'production') await syncTranslations()
const module = path.split('/')[1] || ''
const locale = i18n.locale.value
if (loadedTranslations.has(`${module}:${locale}`)) return
const { data } = await appApi.getI18n(module, locale)
if (data) {
const translationData = JSON.parse(data.i18n)
if (translationData) {
i18n.mergeLocaleMessage(locale, translationData[locale])
loadedTranslations.add(`${module}:${locale}`)
if (process.env.NODE_ENV !== 'production')
console.log('[i18n] Translations loaded', module, locale, translationData)
}
}
}
const route = useRoute()
interface LocaleChangeHandler {
(newLocale: string, oldLocale: string): void
}
watch(() => i18n.locale.value, (() => {
if (process.env.NODE_ENV !== 'production')
console.log('[i18n] Locale changed', i18n.locale.value)
if (route) loadTranslations(route.path)
}) as LocaleChangeHandler)
return {
addTranslation,
loadTranslations,
}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useI18nStore, import.meta.hot))
}
- Add handler to
@/plugins/i18n.ts
to process missing translations
ts
//...
const i18n = createI18n({
//...
missing: (locale, key) => {
if (process.env.NODE_ENV === 'production') return
// @ts-ignore
const i18nStore = useI18nStore()
i18nStore.addTranslation(locale, key)
},
},
)
- Upgrade
@/router/index.ts
with the new lazy loading feature
ts
// ...
router.beforeEach(async (to) => {
// ...
await useI18nStore().loadTranslations(to.path)
// ...
})
// ...