refactor(i18n): lazy load languages (#8456)

closes AF-1397
This commit is contained in:
forehalo
2024-10-10 09:03:06 +00:00
parent f833017e45
commit 9043e6607e
60 changed files with 731 additions and 668 deletions

View File

@@ -0,0 +1,15 @@
import { I18nextProvider } from '@affine/i18n';
import { useService } from '@toeverything/infra';
import { type PropsWithChildren, useEffect } from 'react';
import { I18nService } from './services/i18n';
export function I18nProvider({ children }: PropsWithChildren) {
const i18n = useService(I18nService).i18n;
useEffect(() => {
i18n.init();
}, [i18n]);
return <I18nextProvider i18n={i18n.i18next}>{children}</I18nextProvider>;
}

View File

@@ -0,0 +1,83 @@
import { notify } from '@affine/component';
import { DebugLogger } from '@affine/debug';
import {
getOrCreateI18n,
i18nCompletenesses,
type Language,
SUPPORTED_LANGUAGES,
} from '@affine/i18n';
import type { GlobalCache } from '@toeverything/infra';
import { effect, Entity, fromPromise, LiveData } from '@toeverything/infra';
import { catchError, EMPTY, exhaustMap, mergeMap } from 'rxjs';
export type LanguageInfo = {
key: Language;
name: string;
originalName: string;
completeness: number;
};
const logger = new DebugLogger('i18n');
function mapLanguageInfo(language: Language = 'en'): LanguageInfo {
const languageInfo = SUPPORTED_LANGUAGES[language];
return {
key: language,
name: languageInfo.name,
originalName: languageInfo.originalName,
completeness: i18nCompletenesses[language],
};
}
export class I18n extends Entity {
private readonly i18n = getOrCreateI18n();
get i18next() {
return this.i18n;
}
readonly currentLanguageKey$ = LiveData.from(
this.cache.watch<Language>('i18n_lng'),
undefined
);
readonly currentLanguage$ = this.currentLanguageKey$
.distinctUntilChanged()
.map(mapLanguageInfo);
readonly languageList: Array<LanguageInfo> =
// @ts-expect-error same key indexing
Object.keys(SUPPORTED_LANGUAGES).map(mapLanguageInfo);
constructor(private readonly cache: GlobalCache) {
super();
this.i18n.on('languageChanged', (language: Language) => {
document.documentElement.lang = language;
this.cache.set('i18n_lng', language);
});
}
init() {
this.changeLanguage(this.currentLanguageKey$.value ?? 'en');
}
changeLanguage = effect(
exhaustMap((language: string) =>
fromPromise(() => this.i18n.changeLanguage(language)).pipe(
catchError(error => {
notify({
theme: 'error',
title: 'Failed to change language',
message: 'Error occurs when loading language files',
});
logger.error('Failed to change language', error);
return EMPTY;
}),
mergeMap(() => EMPTY)
)
)
);
}

View File

@@ -0,0 +1,11 @@
import { type Framework, GlobalCache } from '@toeverything/infra';
import { I18nProvider } from './context';
import { I18n, type LanguageInfo } from './entities/i18n';
import { I18nService } from './services/i18n';
export function configureI18nModule(framework: Framework) {
framework.service(I18nService).entity(I18n, [GlobalCache]);
}
export { I18n, I18nProvider, I18nService, type LanguageInfo };

View File

@@ -0,0 +1,7 @@
import { Service } from '@toeverything/infra';
import { I18n } from '../entities/i18n';
export class I18nService extends Service {
public readonly i18n = this.framework.createEntity(I18n);
}