core: use disposable app.i18n.load

This commit is contained in:
undefined committed 2022-11-03 12:37:53 +08:00
1 parent 756b4e0a40
commit ce8b77a6f7
3 files changed
+54 -15

No files matched your search

+5 -4
View File
@@ -1,12 +1,13 @@
/* eslint-disable import/no-dynamic-require */ /* eslint-disable import/no-dynamic-require */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
/* eslint-disable no-eval */ /* eslint-disable no-eval */
import '../lib/index';
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { Context } from '../context'; import { Context } from '../context';
import i18n from '../lib/i18n';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { PRIV } from '../model/builtin'; import { PRIV } from '../model/builtin';
import * as bus from '../service/bus'; import * as bus from '../service/bus';
@@ -79,12 +80,12 @@ export async function locale(pending: string[], fail: string[]) {
if (p && (await fs.stat(p)).isDirectory() && !fail.includes(i)) { if (p && (await fs.stat(p)).isDirectory() && !fail.includes(i)) {
try { try {
const files = await fs.readdir(p); const files = await fs.readdir(p);
const locales = {};
for (const file of files) { for (const file of files) {
const content = await fs.readFile(path.resolve(p, file), 'utf-8'); const content = await fs.readFile(path.resolve(p, file), 'utf-8');
locales[file.split('.')[0]] = yaml.load(content); const dict = yaml.load(content);
if (typeof dict !== 'object' || !dict) throw new Error('Invalid locale file');
app.i18n.load(file.split('.')[0], dict as any);
} }
i18n(locales);
logger.info('Locale init: %s', i); logger.info('Locale init: %s', i);
} catch (e) { } catch (e) {
fail.push(i); fail.push(i);
+48 -10
View File
@@ -1,33 +1,71 @@
const locales = {}; import { Context, Service } from '../context';
const translations: Record<string, Record<string, string>[]> = {};
declare module '../context' {
interface Context {
i18n: I18nService;
}
}
declare global { declare global {
interface String { interface String {
translate: (...languages: string[]) => string; translate: (...languages: string[]) => string;
} }
} }
class I18nService extends Service {
constructor(ctx: Context) {
super(ctx, 'i18n', true);
}
load(lang: string, content: Record<string, string>) {
if (!translations[lang]) translations[lang] = [];
translations[lang].unshift(content);
this.caller?.on('dispose', () => {
translations[lang] = translations[lang].filter((i) => i !== content);
});
}
get(key: string, lang: string) {
if (!translations[lang]) return null;
for (const t of translations[lang] || []) {
if (t[key]) return t[key];
}
return null;
}
}
Context.service('i18n');
app.i18n = new I18nService(app);
String.prototype.translate = function translate(...languages: string[]) { String.prototype.translate = function translate(...languages: string[]) {
if (languages[0]?.startsWith('en')) { if (languages[0]?.startsWith('en')) {
// For most use cases, source text equals to translated text in English. // For most use cases, source text equals to translated text in English.
// So if it doesn't exist, we should use the original text instead of fallback. // So if it doesn't exist, we should use the original text instead of fallback.
return locales[languages[0]]?.[this] || locales['en']?.[this] || this; return app.i18n.get(this, languages[0]) || app.i18n.get(this, 'en') || this;
} }
for (const language of languages) { for (const language of languages.filter(Boolean)) {
if (!language) continue; const curr = app.i18n.get(this, language) || app.i18n.get(this, language.split('_')[0]);
const curr = (locales[language] || {})[this] || (locales[language.split('_')[0]] || {})[this];
if (curr) return curr; if (curr) return curr;
} }
return this; return this;
}; };
/** @deprecated use ctx.i18n.load instead. */
function load(data: Record<string, Record<string, string>>) { function load(data: Record<string, Record<string, string>>) {
for (const i in data) { for (const i in data) app.i18n.load(i, data[i]);
if (!locales[i]) locales[i] = { __id: i, ...data[i] };
else locales[i] = Object.assign(locales[i], data[i]);
}
} }
export = load; export = load;
global.Hydro.locales = locales; global.Hydro.locales = new Proxy(translations, {
get(self, lang: string) {
if (!self[lang]) return {};
return new Proxy(self[lang], {
get(s, key: string) {
return app.i18n.get(lang, key);
},
});
},
}) as any;
global.Hydro.lib.i18n = load; global.Hydro.lib.i18n = load;
+1 -1
View File
@@ -387,7 +387,7 @@ const homework = buildContestRule({
}, },
showScoreboard: () => true, showScoreboard: () => true,
showSelfRecord: () => true, showSelfRecord: () => true,
showRecord: () => true, showRecord: (tdoc, now) => now > tdoc.endAt,
async scoreboardHeader(isExport, _, tdoc, pdict) { async scoreboardHeader(isExport, _, tdoc, pdict) {
const columns: ScoreboardNode[] = [ const columns: ScoreboardNode[] = [
{ type: 'rank', value: _('Rank') }, { type: 'rank', value: _('Rank') },