From 9ccaa9115cf845ec73997d128aaba281baf98632 Mon Sep 17 00:00:00 2001 From: hemengke <23536175@qq.com> Date: Mon, 18 Dec 2023 16:24:03 +0800 Subject: [PATCH] feat: support `autoDetectI18nConfig` option 1. autoDetectI18nConfig is enable default. If enable, plugin will find up vscode settings until reach `stopAt` dir. 2. deprecate `dotVscodePath` because it seems useless. Now use `root` instead of `dotVscodePath` --- README-zh.md | 16 ++--- README.md | 16 ++--- package.json | 1 + playground/spa/vite.config.ts | 2 +- playground/vscode-setting/vite.config.ts | 2 +- pnpm-lock.yaml | 22 +++++-- src/plugin/index.ts | 21 ++++-- src/plugin/locale-detector/LocaleDetector.ts | 2 +- src/plugin/utils/I18nAllyVscodeSetting.ts | 68 ++++++++++++++++++++ src/plugin/utils/init-options.ts | 24 ++++--- src/plugin/utils/vscode-settings.ts | 57 ---------------- tests/LocaleDetector.test.ts | 2 +- 12 files changed, 137 insertions(+), 96 deletions(-) create mode 100644 src/plugin/utils/I18nAllyVscodeSetting.ts delete mode 100644 src/plugin/utils/vscode-settings.ts diff --git a/README-zh.md b/README-zh.md index ff8fed9..357b0a9 100644 --- a/README-zh.md +++ b/README-zh.md @@ -40,14 +40,14 @@ pnpm add vite-plugin-i18n-detector -D **如果已配置i18n.ally,插件会默认读取配置** -| 参数 | 类型 | 默认值 | 描述 | -| ------------- | ----------------- | ------------------------------------------------------------ | ----------------------------------------------- | -| localesPaths | `string[]` | `i18n-ally.localesPaths \|\| ['./src/locales', './locales']` | 存放语言资源的目录地址 | -| root | `string` | `process.cwd()` | 存放语言资源的根目录地址,相对于 `localesPaths` | -| namespace | `boolean` | `i18n-ally.namespace \|\| false` | 是否启用命名空间 | -| pathMatcher | `string` | 自动探测 | 资源文件匹配规则 | -| parserPlugins | `ParserPlugin[]` | `[jsonParser, json5Parser, yamlParser]` | 资源文件解析插件 | -| dotVscodePath | `string | false` | `process.cwd()` | vscode配置文件路径,用于自动探测配置 | +| 参数 | 类型 | 默认值 | 描述 | +| -------------------- | --------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------- | +| localesPaths | `string[]` | `i18n-ally.localesPaths \|\| ['./src/locales', './locales']` | 存放语言资源的目录地址,相对于 `root` | +| root | `string` | `process.cwd()` | 项目根路径 | +| namespace | `boolean` | `i18n-ally.namespace \|\| false` | 是否启用命名空间 | +| pathMatcher | `string` | 自动探测 | 资源文件匹配规则 | +| parserPlugins | `ParserPlugin[]` | `[jsonParser, json5Parser, yamlParser]` | 资源文件解析插件 | +| autoDetectI18nConfig | `boolean \| { stopAt: string }` | `true` | 是否自动探测i18n配置项,如果传入stopAt,则会在指定的目录停止探测 | ## 配置参考 diff --git a/README.md b/README.md index 0d1de6b..c78a317 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,14 @@ pnpm add vite-plugin-i18n-detector -D **If `i18n.ally` is configured, the plugin will read the configuration by default** -| Option | Type | Default | Description | -| ------------- | ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------------ | -| localesPaths | `string[]` | `i18n-ally.localesPaths \|\| ['./src/locales', './locales']` | The directory of language resources | -| root | `string` | `process.cwd()` | The root directory of language resources | -| namespace | `boolean` | `i18n-ally.namespace \|\| false` | Enable namespace | -| pathMatcher | `string` | auto detected by structure | Resource file matching rule | -| parserPlugins | `ParserPlugin[]` | `[jsonParser, json5Parser, yamlParser]` | Resource file parsing plugin | -| dotVscodePath | `string | false` | `process.cwd()` | vscode configuration file path, used for auto detect configuration | +| Option | Type | Default | Description | +| -------------------- | --------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | +| localesPaths | `string[]` | `i18n-ally.localesPaths \|\| ['./src/locales', './locales']` | The directory of language resources, relative to `root` | +| root | `string` | `process.cwd()` | The project root path | +| namespace | `boolean` | `i18n-ally.namespace \|\| false` | Enable namespace | +| pathMatcher | `string` | auto detected by structure | Resource file matching rule | +| parserPlugins | `ParserPlugin[]` | `[jsonParser, json5Parser, yamlParser]` | Resource file parsing plugin | +| autoDetectI18nConfig | `boolean \| { stopAt: string }` | `true` | Whether to automatically detect i18n-ally configuration, if stopAt is passed in, it will stop detecting in the specified directory | ## Config Reference diff --git a/package.json b/package.json index 0bd6659..7480f1f 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "clone-deep": "^4.0.1", "debug": "^4.3.4", "fast-glob": "^3.3.1", + "find-up": "^7.0.0", "js-yaml": "^4.1.0", "json5": "^2.2.3", "language-tags": "^1.0.9", diff --git a/playground/spa/vite.config.ts b/playground/spa/vite.config.ts index a6095ba..200d683 100644 --- a/playground/spa/vite.config.ts +++ b/playground/spa/vite.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ localesPaths: ['./src/locales'], namespace: true, pathMatcher: '{locale}/{namespace}.{ext}', - dotVscodePath: false, + autoDetectI18nConfig: false, }), ], }) diff --git a/playground/vscode-setting/vite.config.ts b/playground/vscode-setting/vite.config.ts index 6685026..0f57027 100644 --- a/playground/vscode-setting/vite.config.ts +++ b/playground/vscode-setting/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ react(), i18nDetector({ root: __dirname, - dotVscodePath: __dirname, + autoDetectI18nConfig: true, }), ], }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96d6d80..1649765 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: fast-glob: specifier: ^3.3.1 version: 3.3.2 + find-up: + specifier: ^7.0.0 + version: 7.0.0 js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -3454,6 +3457,15 @@ packages: path-exists: 5.0.0 dev: true + /find-up@7.0.0: + resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} + engines: {node: '>=18'} + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + unicorn-magic: 0.1.0 + dev: false + /flat-cache@3.0.4: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -4587,7 +4599,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: p-locate: 6.0.0 - dev: true /lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -5358,7 +5369,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: yocto-queue: 1.0.0 - dev: true /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} @@ -5379,7 +5389,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: p-limit: 4.0.0 - dev: true /p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} @@ -5487,7 +5496,6 @@ packages: /path-exists@5.0.0: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} @@ -6857,6 +6865,11 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + dev: false + /uniq@1.0.1: resolution: {integrity: sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==} dev: false @@ -7400,4 +7413,3 @@ packages: /yocto-queue@1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - dev: true diff --git a/src/plugin/index.ts b/src/plugin/index.ts index 0e2f985..bcc3d41 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -13,6 +13,8 @@ export type ParserPlugin = ParserConstructor | undefined export interface I18nDetectorOptions { /** * @description locales directory paths + * + * localesPaths are relative to root * @default * ```js * ['./src/locales', './locales'] @@ -20,8 +22,7 @@ export interface I18nDetectorOptions { */ localesPaths?: string[] /** - * @description localesPaths's root path - * localesPaths are relative to root + * @description root path * @default process.cwd() */ root?: string @@ -63,13 +64,25 @@ export interface I18nDetectorOptions { * @description i18n-ally config root path * @default process.cwd() * - * if dotVscodePath is process.cwd() + * if dotVscodePath is truly, * i18n-ally config path is - * path.resolve(process.cwd(), './vscode/settings.json') by default + * path.resolve(process.cwd(), './vscode/settings.json') * * if false, will not detect i18n-ally config + * + * @deprecated + * use `root` instead */ dotVscodePath?: string | false + /** + * @description auto detect config of vscode extension `i18n-ally` + * @default true + */ + autoDetectI18nConfig?: + | boolean + | { + stopAt: string + } } export async function i18nDetector(opts?: I18nDetectorOptions): Promise { diff --git a/src/plugin/locale-detector/LocaleDetector.ts b/src/plugin/locale-detector/LocaleDetector.ts index d00a6bf..55fbc95 100644 --- a/src/plugin/locale-detector/LocaleDetector.ts +++ b/src/plugin/locale-detector/LocaleDetector.ts @@ -13,7 +13,7 @@ import { PKGNAME, VIRTUAL } from '../utils/constant' import { debug } from '../utils/debugger' import { logger } from '../utils/logger' -export type Config = Omit, 'dotVscodePath'> +export type Config = Omit, 'dotVscodePath' | 'autoDetectI18nConfig'> type PathMatcherType = RegExp diff --git a/src/plugin/utils/I18nAllyVscodeSetting.ts b/src/plugin/utils/I18nAllyVscodeSetting.ts new file mode 100644 index 0000000..3c5e537 --- /dev/null +++ b/src/plugin/utils/I18nAllyVscodeSetting.ts @@ -0,0 +1,68 @@ +import { findUpSync } from 'find-up' +import JSON5 from 'json5' +import fs from 'node:fs' +import path from 'node:path' +import { debug } from './debugger' + +export class I18nAllyVscodeSetting { + static I18N_ALLY_KEY = 'i18n-ally.' + static SETTING_FILE = '.vscode/settings.json' + public i18nConfig: Record | undefined + + constructor( + private readonly _root: string, + private readonly _stopAt: string = _root, + ) { + debug('I18nAllyVscodeSetting - root:', _root) + debug('I18nAllyVscodeSetting - stopAt:', _stopAt) + } + + findUp() { + const settingFile = findUpSync(I18nAllyVscodeSetting.SETTING_FILE, { + type: 'file', + cwd: this._root, + stopAt: this._stopAt, + }) + + debug('findup - settingFile:', settingFile) + return settingFile + } + + init() { + const settings = this.readJsonFile(this.findUp()) + + if (settings) { + const removePrefix = (key: string) => key.replace(I18nAllyVscodeSetting.I18N_ALLY_KEY, '') + const filteredConfig = Object.keys(settings) + .filter((key) => key.startsWith(I18nAllyVscodeSetting.I18N_ALLY_KEY)) + .reduce( + (obj, key) => { + if (key === `${I18nAllyVscodeSetting.I18N_ALLY_KEY}localesPaths`) { + if (Array.isArray(settings[key])) { + obj[removePrefix(key)] = settings[key].map((p: string) => path.resolve(this._root, p)) + } + } else { + obj[removePrefix(key)] = settings[key] + } + return obj + }, + {} as Record, + ) + if (Object.keys(filteredConfig).length) { + this.i18nConfig = filteredConfig + } + } + return this.i18nConfig + } + + readJsonFile(filePath: string | undefined) { + if (filePath) { + try { + return JSON5.parse(fs.readFileSync(filePath, { encoding: 'utf-8' })) + } catch { + return '' + } + } + return '' + } +} diff --git a/src/plugin/utils/init-options.ts b/src/plugin/utils/init-options.ts index 75bd20c..cdf5993 100644 --- a/src/plugin/utils/init-options.ts +++ b/src/plugin/utils/init-options.ts @@ -1,26 +1,30 @@ import { type I18nDetectorOptions } from '..' -import { getI18nAllyConfigByKey, readI18nAllyConfig } from './vscode-settings' +import { I18nAllyVscodeSetting } from './I18nAllyVscodeSetting' +import { debug } from './debugger' const DEFAULT_OPTIONS: I18nDetectorOptions = { localesPaths: ['./src/locales', './locales'], root: process.cwd(), namespace: false, - dotVscodePath: process.cwd(), + autoDetectI18nConfig: true, } function getDefaultOptions(options?: I18nDetectorOptions): I18nDetectorOptions { - let i18AllyConfig: Record | undefined = undefined - if (options?.dotVscodePath !== false) { - // detect vscode settings of i18n-ally - i18AllyConfig = readI18nAllyConfig(options?.dotVscodePath || (DEFAULT_OPTIONS.dotVscodePath as string)) + if (options?.dotVscodePath !== undefined) { + console.warn(`dotVscodePath is deprecated, please use 'root' instead`) } - if (i18AllyConfig) { + if (options?.autoDetectI18nConfig) { + const stopAt = typeof options.autoDetectI18nConfig === 'object' ? options.autoDetectI18nConfig.stopAt : undefined + const i18nAlly = new I18nAllyVscodeSetting(options?.root || (DEFAULT_OPTIONS.root as string), stopAt).init() + + debug('i18n-ally config:', i18nAlly) + return { - localesPaths: getI18nAllyConfigByKey(i18AllyConfig, 'localesPaths') ?? DEFAULT_OPTIONS.localesPaths, - pathMatcher: getI18nAllyConfigByKey(i18AllyConfig, 'pathMatcher'), - namespace: getI18nAllyConfigByKey(i18AllyConfig, 'namespace') ?? DEFAULT_OPTIONS.namespace, ...DEFAULT_OPTIONS, + localesPaths: i18nAlly?.['localesPaths'] ?? DEFAULT_OPTIONS.localesPaths, + pathMatcher: i18nAlly?.['pathMatcher'] ?? DEFAULT_OPTIONS.pathMatcher, + namespace: i18nAlly?.['namespace'] ?? DEFAULT_OPTIONS.namespace, } } diff --git a/src/plugin/utils/vscode-settings.ts b/src/plugin/utils/vscode-settings.ts deleted file mode 100644 index 1df6519..0000000 --- a/src/plugin/utils/vscode-settings.ts +++ /dev/null @@ -1,57 +0,0 @@ -// import { findUpSync } from 'find-up' -import JSON5 from 'json5' -import fs from 'node:fs' -import path from 'node:path' - -export const SETTING_FILE = '.vscode/settings.json' - -// TODO: need auto findup? or user custom? -// export function findupVscodeSettings(cwd?: string) { -// const settingFile = findUpSync(SETTING_FILE, { -// type: 'file', -// cwd, -// // TODO: user custom -// stopAt: '.git', -// }) - -// return settingFile -// } - -export function readFile(filePath?: string) { - if (filePath) { - return JSON5.parse(fs.readFileSync(filePath, { encoding: 'utf-8' })) - } - return '' -} - -export const I18N_ALLY_KEY = 'i18n-ally.' - -export function readI18nAllyConfig(dotVscodePath: string) { - const settings = readFile(path.resolve(dotVscodePath, SETTING_FILE)) - - if (settings) { - const filteredConfig = Object.keys(settings) - .filter((key) => key.startsWith(I18N_ALLY_KEY)) - .reduce( - (obj, key) => { - if (key === `${I18N_ALLY_KEY}localesPaths`) { - if (Array.isArray(settings[key])) { - obj[key] = settings[key].map((p: string) => path.resolve(dotVscodePath, p)) - } - } else { - obj[key] = settings[key] - } - return obj - }, - {} as Record, - ) - if (Object.keys(filteredConfig).length) { - return filteredConfig - } - } - return undefined -} - -export function getI18nAllyConfigByKey(config: Record, key: string) { - return config[`${I18N_ALLY_KEY}${key}`] -} diff --git a/tests/LocaleDetector.test.ts b/tests/LocaleDetector.test.ts index 5fb5fbc..d099ced 100644 --- a/tests/LocaleDetector.test.ts +++ b/tests/LocaleDetector.test.ts @@ -11,7 +11,7 @@ describe('LocaleDetector - Dir mode', () => { root: path.resolve(__dirname, './fixtures/'), localesPaths: [path.resolve(__dirname, './fixtures/locales/')], namespace: true, - dotVscodePath: false, + autoDetectI18nConfig: false, }) localeDetector = new LocaleDetector(options)