From ce8b77a6f70b4eee2bd448a89ce15c680f071bcc Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 3 Nov 2022 12:37:53 +0800 Subject: [PATCH] core: use disposable app.i18n.load --- packages/hydrooj/src/entry/common.ts | 9 +++-- packages/hydrooj/src/lib/i18n.ts | 58 ++++++++++++++++++++++----- packages/hydrooj/src/model/contest.ts | 2 +- 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/packages/hydrooj/src/entry/common.ts b/packages/hydrooj/src/entry/common.ts index 20a96de90..81aff2836 100644 --- a/packages/hydrooj/src/entry/common.ts +++ b/packages/hydrooj/src/entry/common.ts @@ -1,12 +1,13 @@ /* eslint-disable import/no-dynamic-require */ /* eslint-disable no-await-in-loop */ /* eslint-disable no-eval */ +import '../lib/index'; + import os from 'os'; import path from 'path'; import fs from 'fs-extra'; import yaml from 'js-yaml'; import { Context } from '../context'; -import i18n from '../lib/i18n'; import { Logger } from '../logger'; import { PRIV } from '../model/builtin'; 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)) { try { const files = await fs.readdir(p); - const locales = {}; for (const file of files) { 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); } catch (e) { fail.push(i); diff --git a/packages/hydrooj/src/lib/i18n.ts b/packages/hydrooj/src/lib/i18n.ts index 022a9d614..6c911032e 100644 --- a/packages/hydrooj/src/lib/i18n.ts +++ b/packages/hydrooj/src/lib/i18n.ts @@ -1,33 +1,71 @@ -const locales = {}; +import { Context, Service } from '../context'; +const translations: Record[]> = {}; + +declare module '../context' { + interface Context { + i18n: I18nService; + } +} declare global { interface String { translate: (...languages: string[]) => string; } } +class I18nService extends Service { + constructor(ctx: Context) { + super(ctx, 'i18n', true); + } + + load(lang: string, content: Record) { + 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[]) { if (languages[0]?.startsWith('en')) { // 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. - 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) { - if (!language) continue; - const curr = (locales[language] || {})[this] || (locales[language.split('_')[0]] || {})[this]; + for (const language of languages.filter(Boolean)) { + const curr = app.i18n.get(this, language) || app.i18n.get(this, language.split('_')[0]); if (curr) return curr; } return this; }; +/** @deprecated use ctx.i18n.load instead. */ function load(data: Record>) { - for (const i in data) { - if (!locales[i]) locales[i] = { __id: i, ...data[i] }; - else locales[i] = Object.assign(locales[i], data[i]); - } + for (const i in data) app.i18n.load(i, data[i]); } 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; diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 7a05e1a41..b2a3f1790 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -387,7 +387,7 @@ const homework = buildContestRule({ }, showScoreboard: () => true, showSelfRecord: () => true, - showRecord: () => true, + showRecord: (tdoc, now) => now > tdoc.endAt, async scoreboardHeader(isExport, _, tdoc, pdict) { const columns: ScoreboardNode[] = [ { type: 'rank', value: _('Rank') },