11import type { HubUiLocale } from '../../locales'
22import { usePreferredLanguages , useStorage } from '@vueuse/core'
3- import { computed , ref } from 'vue'
3+ import { computed , ref , shallowReactive , watch } from 'vue'
44import { DEFAULT_LOCALE , matchLocale } from '../../locales'
5- import de from './locales/de.json'
65import en from './locales/en.json'
7- import es from './locales/es.json'
8- import fr from './locales/fr.json'
9- import ja from './locales/ja.json'
10- import ko from './locales/ko.json'
11- import ptBR from './locales/pt-BR.json'
12- import ru from './locales/ru.json'
13- import zhCN from './locales/zh-CN.json'
14- import zhTW from './locales/zh-TW.json'
156
167/** Every UI string has a key in `en.json`; the other files translate them. */
178export type MessageKey = keyof typeof en
189
19- export const messages : Record < HubUiLocale , Partial < Record < MessageKey , string > > > = {
20- 'en' : en ,
21- 'zh-CN' : zhCN ,
22- 'zh-TW' : zhTW ,
23- ja,
24- ko,
25- es,
26- fr,
27- de,
28- 'pt-BR' : ptBR ,
29- ru,
10+ type Messages = Partial < Record < MessageKey , string > >
11+
12+ /** English is bundled: it is the fallback, so `t()` never waits on a network. */
13+ const loaders : Record < Exclude < HubUiLocale , 'en' > , ( ) => Promise < { default : Messages } > > = {
14+ 'zh-CN' : ( ) => import ( './locales/zh-CN.json' ) ,
15+ 'zh-TW' : ( ) => import ( './locales/zh-TW.json' ) ,
16+ 'ja' : ( ) => import ( './locales/ja.json' ) ,
17+ 'ko' : ( ) => import ( './locales/ko.json' ) ,
18+ 'es' : ( ) => import ( './locales/es.json' ) ,
19+ 'fr' : ( ) => import ( './locales/fr.json' ) ,
20+ 'de' : ( ) => import ( './locales/de.json' ) ,
21+ 'pt-BR' : ( ) => import ( './locales/pt-BR.json' ) ,
22+ 'ru' : ( ) => import ( './locales/ru.json' ) ,
23+ }
24+
25+ const loaded = shallowReactive < Partial < Record < HubUiLocale , Messages > > > ( { en } )
26+ const pending = new Map < HubUiLocale , Promise < void > > ( )
27+
28+ /**
29+ * Fetch the chunk for `code` once. `t()` reads `loaded` reactively, so the UI
30+ * shows English until the chunk lands, then re-renders in the new language.
31+ */
32+ export function loadLocale ( code : HubUiLocale ) : Promise < void > {
33+ if ( loaded [ code ] )
34+ return Promise . resolve ( )
35+ let request = pending . get ( code )
36+ if ( ! request ) {
37+ request = loaders [ code as keyof typeof loaders ] ( )
38+ . then ( ( module ) => {
39+ loaded [ code ] = module . default
40+ } )
41+ . catch ( ( ) => {
42+ // Stay on English, and allow a retry on the next pick.
43+ } )
44+ . finally ( ( ) => pending . delete ( code ) )
45+ pending . set ( code , request )
46+ }
47+ return request
3048}
3149
3250export type LocalePreference = 'auto' | HubUiLocale
@@ -57,6 +75,8 @@ export const locale = computed<HubUiLocale>(() => {
5775 return DEFAULT_LOCALE
5876} )
5977
78+ watch ( locale , code => void loadLocale ( code ) , { immediate : true } )
79+
6080/** Seed the `auto` choice from `ConnectionMeta.configs.ui.locale`. */
6181export function setHostLocale ( tag : string | undefined ) : void {
6282 hostLocale . value = matchLocale ( tag )
@@ -68,7 +88,7 @@ export function setLocalePreference(preference: LocalePreference): void {
6888
6989/** Translate `key` in the current locale, filling `{name}` slots from `params`. */
7090export function t ( key : MessageKey , params ?: Record < string , string | number > ) : string {
71- const message = messages [ locale . value ] [ key ] ?? en [ key ]
91+ const message = loaded [ locale . value ] ?. [ key ] ?? en [ key ]
7292 if ( ! params )
7393 return message
7494 return message . replace ( / \{ ( \w + ) \} / g, ( slot , name : string ) => name in params ? String ( params [ name ] ) : slot )
0 commit comments