From fd3438a20d57c7801a515a8bbe3cc2cec4aa6515 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 2 Dec 2023 02:13:49 +0900 Subject: [PATCH] feat(i18n): i18n auto-importer --- electron.vite.config.ts | 4 +++ src/i18n/index.ts | 2 +- src/i18n/resources/@types/index.ts | 11 +++++++ src/i18n/resources/index.ts | 11 ------- src/index.ts | 4 +-- src/menu.ts | 6 ++-- src/virtual-module.d.ts | 6 ++++ vite-plugins/i18n-importer.ts | 47 ++++++++++++++++++++++++++++++ 8 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 src/i18n/resources/@types/index.ts delete mode 100644 src/i18n/resources/index.ts create mode 100644 vite-plugins/i18n-importer.ts diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 3d558c99..c527c26c 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -9,6 +9,7 @@ import { pluginVirtualModuleGenerator } from './vite-plugins/plugin-importer'; import pluginLoader from './vite-plugins/plugin-loader'; import type { UserConfig } from 'vite'; +import { i18nImporter } from './vite-plugins/i18n-importer'; const resolveAlias = { '@': resolve(__dirname, './src'), @@ -21,6 +22,7 @@ export default defineConfig({ plugins: [ pluginLoader('backend'), viteResolve({ + 'virtual:i18n': i18nImporter(), 'virtual:plugins': pluginVirtualModuleGenerator('main'), }), ], @@ -65,6 +67,7 @@ export default defineConfig({ plugins: [ pluginLoader('preload'), viteResolve({ + 'virtual:i18n': i18nImporter(), 'virtual:plugins': pluginVirtualModuleGenerator('preload'), }), ], @@ -108,6 +111,7 @@ export default defineConfig({ plugins: [ pluginLoader('renderer'), viteResolve({ + 'virtual:i18n': i18nImporter(), 'virtual:plugins': pluginVirtualModuleGenerator('renderer'), }), ], diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 61cfe1e7..c43f346f 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,6 +1,6 @@ import i18next, { init, t as i18t, changeLanguage } from 'i18next'; -import { languageResources } from '@/i18n/resources'; +import { languageResources } from 'virtual:i18n'; export const loadI18n = async () => await init({ diff --git a/src/i18n/resources/@types/index.ts b/src/i18n/resources/@types/index.ts new file mode 100644 index 00000000..e5a200fa --- /dev/null +++ b/src/i18n/resources/@types/index.ts @@ -0,0 +1,11 @@ +export interface LanguageResources { + [lang: string]: { + translation: Record & { + language: { + name: string; + 'local-name': string; + code: string; + }; + }; + }; +} diff --git a/src/i18n/resources/index.ts b/src/i18n/resources/index.ts deleted file mode 100644 index b624df7a..00000000 --- a/src/i18n/resources/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import enJson from './en.json'; -import koJson from './ko.json'; - -export const languageResources = { - en: { - translation: enJson - }, - ko: { - translation: koJson - } -}; diff --git a/src/index.ts b/src/index.ts index 17ad8f0e..49181a5d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,6 +26,8 @@ import { deepEqual } from 'fast-equals'; import { allPlugins, mainPlugins } from 'virtual:plugins'; +import { languageResources } from 'virtual:i18n'; + import config from '@/config'; import { refreshMenu, setApplicationMenu } from '@/menu'; @@ -52,8 +54,6 @@ import { import { LoggerPrefix } from '@/utils'; import { loadI18n, setLanguage, t } from '@/i18n'; -import { languageResources } from '@/i18n/resources'; - import type { PluginConfig } from '@/types/plugins'; // Catch errors and log them diff --git a/src/menu.ts b/src/menu.ts index 455cfc7d..42cf3d0f 100644 --- a/src/menu.ts +++ b/src/menu.ts @@ -11,6 +11,8 @@ import prompt from 'custom-electron-prompt'; import { allPlugins } from 'virtual:plugins'; +import { languageResources } from 'virtual:i18n'; + import config from './config'; import { restart } from './providers/app-controls'; @@ -19,7 +21,7 @@ import promptOptions from './providers/prompt-options'; import { getAllMenuTemplate, loadAllMenuPlugins } from './loader/menu'; import { setLanguage, t } from '@/i18n'; -import { languageResources } from '@/i18n/resources'; + export type MenuTemplate = Electron.MenuItemConstructorOptions[]; @@ -104,7 +106,7 @@ export const mainMenuTemplate = async ( return pluginEnabledMenu(id, pluginLabel, true, innerRefreshMenu); }); - const availableLanguages = Object.keys(languageResources) as unknown as (keyof typeof languageResources)[]; + const availableLanguages = Object.keys(languageResources); return [ { diff --git a/src/virtual-module.d.ts b/src/virtual-module.d.ts index ffb06766..ab940f3d 100644 --- a/src/virtual-module.d.ts +++ b/src/virtual-module.d.ts @@ -12,3 +12,9 @@ declare module 'virtual:plugins' { Omit >; } + +declare module 'virtual:i18n' { + import type { LanguageResources } from '@/i18n/resources/@types'; + + export const languageResources: LanguageResources; +} diff --git a/vite-plugins/i18n-importer.ts b/vite-plugins/i18n-importer.ts new file mode 100644 index 00000000..f2a137aa --- /dev/null +++ b/vite-plugins/i18n-importer.ts @@ -0,0 +1,47 @@ +import { basename, relative, resolve, extname } from 'node:path'; + +import { globSync } from 'glob'; +import { Project } from 'ts-morph'; + +const snakeToCamel = (text: string) => + text.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase()); + +export const i18nImporter = () => { + const project = new Project({ + tsConfigFilePath: resolve(__dirname, '..', 'tsconfig.json'), + skipAddingFilesFromTsConfig: true, + skipLoadingLibFiles: true, + skipFileDependencyResolution: true, + }); + + const srcPath = resolve(__dirname, '..', 'src'); + const plugins = globSync([ + 'src/i18n/resources/*.json', + ]).map((path) => { + const nameWithExt = basename(path); + const name = nameWithExt.replace(extname(nameWithExt), ''); + + return { name, path }; + }); + + const src = project.createSourceFile('vm:i18n', (writer) => { + // prettier-ignore + for (const { name, path } of plugins) { + const relativePath = relative(resolve(srcPath, '..'), path).replace(/\\/g, '/'); + writer.writeLine(`import ${snakeToCamel(name)}Json from "./${relativePath}";`); + } + + writer.blankLine(); + + writer.writeLine('export const languageResources = {'); + for (const { name } of plugins) { + writer.writeLine(` "${name}": {`); + writer.writeLine(` translation: ${snakeToCamel(name)}Json,`); + writer.writeLine(' },'); + } + writer.writeLine('};'); + writer.blankLine(); + }); + + return src.getText(); +};