From 1127b805695db319d5de0071b25269e692f49b44 Mon Sep 17 00:00:00 2001 From: hemengke <23536175@qq.com> Date: Sat, 16 Dec 2023 01:11:38 +0800 Subject: [PATCH] wip: fix test --- .vscode/settings.json | 2 +- package.json | 1 + playground/spa/.gitignore | 1 + playground/spa/__tests__/spa.spec.ts | 7 +- playground/spa/vite.config.ts | 3 + playground/vscode-setting/.gitignore | 25 ++++ .../vscode-setting/.vscode/settings.json | 6 + .../__tests__/vscode-setting.ts | 128 ++++++++++++++++++ playground/vscode-setting/index.html | 16 +++ playground/vscode-setting/package.json | 27 ++++ playground/vscode-setting/public/vite.svg | 1 + playground/vscode-setting/src/App.css | 42 ++++++ playground/vscode-setting/src/App.tsx | 62 +++++++++ .../vscode-setting/src/assets/react.svg | 1 + playground/vscode-setting/src/const.ts | 2 + playground/vscode-setting/src/index.css | 69 ++++++++++ .../vscode-setting/src/locales/de/more.json | 7 + .../vscode-setting/src/locales/de/test.json | 4 + playground/vscode-setting/src/locales/en.json | 3 + .../vscode-setting/src/locales/en/more.json | 10 ++ .../vscode-setting/src/locales/en/test.json | 4 + .../vscode-setting/src/locales/en/unused.json | 1 + .../src/locales/zh-tw/test.json | 4 + .../vscode-setting/src/locales/zh/test.json | 4 + playground/vscode-setting/src/main.tsx | 68 ++++++++++ playground/vscode-setting/src/vite-env.d.ts | 1 + playground/vscode-setting/vite.config.ts | 14 ++ pnpm-lock.yaml | 80 +++++++++-- src/plugin/index.ts | 24 +++- src/plugin/locale-detector/LocaleDetector.ts | 14 +- src/plugin/utils/hmr.ts | 2 - src/plugin/utils/init-options.ts | 39 ++---- src/plugin/utils/vscode-settings.ts | 23 ++-- 33 files changed, 623 insertions(+), 72 deletions(-) create mode 100644 playground/vscode-setting/.gitignore create mode 100644 playground/vscode-setting/.vscode/settings.json create mode 100644 playground/vscode-setting/__tests__/vscode-setting.ts create mode 100644 playground/vscode-setting/index.html create mode 100644 playground/vscode-setting/package.json create mode 100644 playground/vscode-setting/public/vite.svg create mode 100644 playground/vscode-setting/src/App.css create mode 100644 playground/vscode-setting/src/App.tsx create mode 100644 playground/vscode-setting/src/assets/react.svg create mode 100644 playground/vscode-setting/src/const.ts create mode 100644 playground/vscode-setting/src/index.css create mode 100644 playground/vscode-setting/src/locales/de/more.json create mode 100644 playground/vscode-setting/src/locales/de/test.json create mode 100644 playground/vscode-setting/src/locales/en.json create mode 100644 playground/vscode-setting/src/locales/en/more.json create mode 100644 playground/vscode-setting/src/locales/en/test.json create mode 100644 playground/vscode-setting/src/locales/en/unused.json create mode 100644 playground/vscode-setting/src/locales/zh-tw/test.json create mode 100644 playground/vscode-setting/src/locales/zh/test.json create mode 100644 playground/vscode-setting/src/main.tsx create mode 100644 playground/vscode-setting/src/vite-env.d.ts create mode 100644 playground/vscode-setting/vite.config.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 92cdd8b..ca757a2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "i18n-ally.localesPaths": ["playground/spa/src/locales"], - "i18n-ally.keystyle": "flat", + "i18n-ally.keystyle": "nested", "i18n-ally.enabledParsers": ["json", "json5", "yaml"], "i18n-ally.enabledFrameworks": ["react", "i18next", "react-i18next"], "i18n-ally.namespace": true, diff --git a/package.json b/package.json index 4f6765c..d7382c0 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "@types/debug": "^4.1.12", "@types/js-yaml": "^4.0.9", "@types/language-tags": "^1.0.4", + "@types/node": "^20.10.4", "@types/react": "^18.2.45", "bumpp": "^9.2.0", "conventional-changelog-cli": "^4.1.0", diff --git a/playground/spa/.gitignore b/playground/spa/.gitignore index a547bf3..49ef0bd 100644 --- a/playground/spa/.gitignore +++ b/playground/spa/.gitignore @@ -15,6 +15,7 @@ dist-ssr # Editor directories and files .vscode/* !.vscode/extensions.json +!.vscode/settings.json .idea .DS_Store *.suo diff --git a/playground/spa/__tests__/spa.spec.ts b/playground/spa/__tests__/spa.spec.ts index d2c7377..bfb1efe 100644 --- a/playground/spa/__tests__/spa.spec.ts +++ b/playground/spa/__tests__/spa.spec.ts @@ -86,9 +86,8 @@ describe.skipIf(isBuild)('server related tests', () => { describe('hmr', () => { test('should trigger hmr when locale files changed', async () => { await page.click('#en') - editFile('src/locales/en/test.json', (text) => { - return text.replace(`"key": "en"`, `"key": "updated en"`) - }) + + editFile('src/locales/en/test.json', (text) => text.replace(`"key": "en"`, `"key": "updated en"`)) await untilUpdated(() => page.textContent('#language'), 'updated en') }) @@ -120,7 +119,7 @@ describe.skipIf(isBuild)('server related tests', () => { test('should page reload when locale file added', async () => { const request = page.waitForResponse(/src\/App\.tsx$/, { timeout: 500 }) - addFile('src/locales/en/test2.json', '{}') + addFile('src/locales/en/test.json', '{}') const response = await request.then(() => ({ status: () => 1 })) expect(response.status()).toBe(1) }) diff --git a/playground/spa/vite.config.ts b/playground/spa/vite.config.ts index dd8abbf..25fb4ca 100644 --- a/playground/spa/vite.config.ts +++ b/playground/spa/vite.config.ts @@ -9,6 +9,9 @@ export default defineConfig({ react(), i18nDetector({ root: __dirname, + localesPaths: ['./src/locales'], + namespace: true, + dotVscodePath: false, }), ], }) diff --git a/playground/vscode-setting/.gitignore b/playground/vscode-setting/.gitignore new file mode 100644 index 0000000..49ef0bd --- /dev/null +++ b/playground/vscode-setting/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/playground/vscode-setting/.vscode/settings.json b/playground/vscode-setting/.vscode/settings.json new file mode 100644 index 0000000..acb66b8 --- /dev/null +++ b/playground/vscode-setting/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "i18n-ally.localesPaths": ["src/locales"], + "i18n-ally.keystyle": "nested", + "i18n-ally.namespace": true, + "i18n-ally.pathMatcher": "{locale}/{namespaces}.json" +} diff --git a/playground/vscode-setting/__tests__/vscode-setting.ts b/playground/vscode-setting/__tests__/vscode-setting.ts new file mode 100644 index 0000000..a4a174d --- /dev/null +++ b/playground/vscode-setting/__tests__/vscode-setting.ts @@ -0,0 +1,128 @@ +import { + addFile, + editFile, + isBuild, + isServe, + page, + removeDir, + removeFile, + renameDir, + untilBrowserLogAfter, + untilUpdated, + viteTestUrl, +} from '~utils' +import { describe, expect, test } from 'vitest' + +describe('e2e', () => { + test('should render en by default', async () => { + expect(await page.textContent('#language')).toBe('en') + }) + + test.runIf(isServe)('should lazyload locale js after click', async () => { + let request = page.waitForResponse((res) => res.url().includes('@i18n/virtual:i18n-zh'), { + timeout: 500, + }) + await page.click('#zh') + let response = await request.then(() => ({ status: () => 1 })) + expect(response.status()).toBe(1) + + request = page.waitForResponse((res) => res.url().includes('@i18n/virtual:i18n-de'), { + timeout: 500, + }) + await page.click('#de') + response = await request.then(() => ({ status: () => 1 })) + expect(response.status()).toBe(1) + }) + + test('should change language', async () => { + await page.click('#zh') + + await untilUpdated(() => page.textContent('#language'), '中文') + + await page.click('#de') + + await untilUpdated(() => page.textContent('#language'), 'Deutsch') + + await page.click('#en') + }) + + test('should set html attribute lang', async () => { + expect(await page.getAttribute('html', 'lang')).toBe('en') + + await page.click('#zh') + + expect(await page.getAttribute('html', 'lang')).toBe('zh') + + await page.click('#de') + + expect(await page.getAttribute('html', 'lang')).toBe('de') + + await page.click('#en') + }) + + test('should set url query', async () => { + await page.click('#zh') + let currentUrl = page.url() + let urlSearchParams = new URLSearchParams(currentUrl.split('?')[1]) + let lang = urlSearchParams.get('lang') + expect(lang).toBe('zh') + + await page.click('#en') + currentUrl = page.url() + urlSearchParams = new URLSearchParams(currentUrl.split('?')[1]) + lang = urlSearchParams.get('lang') + expect(lang).toBe('en') + }) + + test('should fallback to fallbackLng when language is not found', async () => { + await untilBrowserLogAfter( + () => page.goto(`${viteTestUrl}/?lang=not-exist`), + /.*Language 'not-exist' is detected.*/, + ) + }) +}) + +describe.skipIf(isBuild)('server related tests', () => { + describe('hmr', () => { + test('should trigger hmr when locale files changed', async () => { + await page.click('#en') + editFile('src/locales/en/test.json', (text) => { + return text.replace(`"key": "en"`, `"key": "updated en"`) + }) + + await untilUpdated(() => page.textContent('#language'), 'updated en') + }) + + test('should page reload when locale dir removed', async () => { + const request = page.waitForResponse(/src\/App\.tsx$/, { timeout: 500 }) + removeDir('src/locales/zh-tw/') + const response = await request.then(() => ({ status: () => 1 })) + expect(response.status()).toBe(1) + }) + + test('should page reload when locale files removed', async () => { + const request = page.waitForResponse(/src\/App\.tsx$/, { timeout: 500 }) + removeFile('src/locales/de/test.json') + const response = await request.then(() => ({ status: () => 1 })) + expect(response.status()).toBe(1) + }) + + test('should page reload when locale dir name changed', async () => { + const request = page.waitForResponse(/src\/App\.tsx$/, { timeout: 500 }) + + renameDir('src/locales/en/', 'src/locales/en-US/') + let response = await request.then(() => ({ status: () => 1 })) + expect(response.status()).toBe(1) + renameDir('src/locales/en-US/', 'src/locales/en/') + response = await request.then(() => ({ status: () => 1 })) + expect(response.status()).toBe(1) + }) + + test('should page reload when locale file added', async () => { + const request = page.waitForResponse(/src\/App\.tsx$/, { timeout: 500 }) + addFile('src/locales/en/test.json', '{}') + const response = await request.then(() => ({ status: () => 1 })) + expect(response.status()).toBe(1) + }) + }) +}) diff --git a/playground/vscode-setting/index.html b/playground/vscode-setting/index.html new file mode 100644 index 0000000..2f0a6af --- /dev/null +++ b/playground/vscode-setting/index.html @@ -0,0 +1,16 @@ + + + + + + + + vite-plugin-i18n-detector-demo + + + +
+ + + + diff --git a/playground/vscode-setting/package.json b/playground/vscode-setting/package.json new file mode 100644 index 0000000..22a1efd --- /dev/null +++ b/playground/vscode-setting/package.json @@ -0,0 +1,27 @@ +{ + "name": "spa", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "cross-env DEBUG=vite-plugin-i18n* vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "i18next": "^23.5.1", + "i18next-browser-languagedetector": "^7.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-i18next": "^13.2.2", + "vite-plugin-i18n-detector": "workspace:*" + }, + "devDependencies": { + "@types/react": "^18.2.28", + "@types/react-dom": "^18.2.13", + "@vitejs/plugin-react": "^4.1.0", + "cross-env": "^7.0.3", + "typescript": "^5.2.2", + "vite": "^4.4.11" + } +} diff --git a/playground/vscode-setting/public/vite.svg b/playground/vscode-setting/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/playground/vscode-setting/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/playground/vscode-setting/src/App.css b/playground/vscode-setting/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/playground/vscode-setting/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/playground/vscode-setting/src/App.tsx b/playground/vscode-setting/src/App.tsx new file mode 100644 index 0000000..5e689dc --- /dev/null +++ b/playground/vscode-setting/src/App.tsx @@ -0,0 +1,62 @@ +import { useReducer } from 'react' +import { useTranslation } from 'react-i18next' +import './App.css' + +function App() { + const { t, i18n } = useTranslation() + + const [, update] = useReducer((x) => ++x, 0) + + return ( +
+

{t('test.tip')}

+ +
+ {t('test.key')} +
+ + + + + +
+ ) +} + +export default App diff --git a/playground/vscode-setting/src/assets/react.svg b/playground/vscode-setting/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/playground/vscode-setting/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/playground/vscode-setting/src/const.ts b/playground/vscode-setting/src/const.ts new file mode 100644 index 0000000..5241028 --- /dev/null +++ b/playground/vscode-setting/src/const.ts @@ -0,0 +1,2 @@ +export const lookupTarget = 'lang' +export const fallbackLng = 'en' diff --git a/playground/vscode-setting/src/index.css b/playground/vscode-setting/src/index.css new file mode 100644 index 0000000..2c3fac6 --- /dev/null +++ b/playground/vscode-setting/src/index.css @@ -0,0 +1,69 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/playground/vscode-setting/src/locales/de/more.json b/playground/vscode-setting/src/locales/de/more.json new file mode 100644 index 0000000..1f68356 --- /dev/null +++ b/playground/vscode-setting/src/locales/de/more.json @@ -0,0 +1,7 @@ +{ + "a": { + "b": { + "c": "more info" + } + } +} diff --git a/playground/vscode-setting/src/locales/de/test.json b/playground/vscode-setting/src/locales/de/test.json new file mode 100644 index 0000000..eaf7712 --- /dev/null +++ b/playground/vscode-setting/src/locales/de/test.json @@ -0,0 +1,4 @@ +{ + "tip": "Bitte öffnen Sie die Konsole, um js-Ressourcen zu filtern. Beim Wechseln der Sprache können Sie das verzögerte Laden von Sprachressourcendateien sehen", + "key": "Deutsch" +} diff --git a/playground/vscode-setting/src/locales/en.json b/playground/vscode-setting/src/locales/en.json new file mode 100644 index 0000000..a8ab3ac --- /dev/null +++ b/playground/vscode-setting/src/locales/en.json @@ -0,0 +1,3 @@ +{ + "namespace": "value" +} diff --git a/playground/vscode-setting/src/locales/en/more.json b/playground/vscode-setting/src/locales/en/more.json new file mode 100644 index 0000000..47795d0 --- /dev/null +++ b/playground/vscode-setting/src/locales/en/more.json @@ -0,0 +1,10 @@ +{ + "a": { + "b": { + "c": "more info default" + } + }, + "key": { + "key-1": "value-1" + } +} diff --git a/playground/vscode-setting/src/locales/en/test.json b/playground/vscode-setting/src/locales/en/test.json new file mode 100644 index 0000000..4ceccd8 --- /dev/null +++ b/playground/vscode-setting/src/locales/en/test.json @@ -0,0 +1,4 @@ +{ + "tip": "Open devtool to filter js resources. When you switch languages, you can see the lazy loading of language resource files", + "key": "en" +} diff --git a/playground/vscode-setting/src/locales/en/unused.json b/playground/vscode-setting/src/locales/en/unused.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/playground/vscode-setting/src/locales/en/unused.json @@ -0,0 +1 @@ +{} diff --git a/playground/vscode-setting/src/locales/zh-tw/test.json b/playground/vscode-setting/src/locales/zh-tw/test.json new file mode 100644 index 0000000..e97e833 --- /dev/null +++ b/playground/vscode-setting/src/locales/zh-tw/test.json @@ -0,0 +1,4 @@ +{ + "tip": "請開啟控制台篩選js資源,切換語言時可看到懶載入語言資源文件", + "key": "繁体中文" +} diff --git a/playground/vscode-setting/src/locales/zh/test.json b/playground/vscode-setting/src/locales/zh/test.json new file mode 100644 index 0000000..1185c82 --- /dev/null +++ b/playground/vscode-setting/src/locales/zh/test.json @@ -0,0 +1,4 @@ +{ + "tip": "请打开控制台筛选js资源,切换语言时可看到懒加载语言资源文件", + "key": "中文" +} diff --git a/playground/vscode-setting/src/main.tsx b/playground/vscode-setting/src/main.tsx new file mode 100644 index 0000000..cfa3803 --- /dev/null +++ b/playground/vscode-setting/src/main.tsx @@ -0,0 +1,68 @@ +import i18next from 'i18next' +import LanguageDetector from 'i18next-browser-languagedetector' +import React from 'react' +import ReactDOM from 'react-dom/client' +import { initReactI18next } from 'react-i18next' +import { setupI18n } from 'vite-plugin-i18n-detector/client' +import App from './App' +import { fallbackLng, lookupTarget } from './const' +import './index.css' + +const root = ReactDOM.createRoot(document.querySelector('#root') as HTMLElement) + +i18next + .use(LanguageDetector) + .use(initReactI18next) + .init({ + returnNull: false, + react: { + useSuspense: true, + }, + debug: import.meta.env.DEV, + resources: {}, + nsSeparator: '.', + keySeparator: false, + interpolation: { + escapeValue: false, + }, + lowerCaseLng: true, + fallbackLng, + detection: { + order: ['querystring', 'cookie', 'localStorage', 'sessionStorage', 'navigator'], + caches: ['localStorage', 'sessionStorage', 'cookie'], + lookupQuerystring: lookupTarget, + lookupLocalStorage: lookupTarget, + lookupSessionStorage: lookupTarget, + lookupCookie: lookupTarget, + }, + }) + +const { loadResourceByLang } = setupI18n({ + language: i18next.language, + onInited() { + root.render( + + + , + ) + }, + onResourceLoaded: (langs, currentLang) => { + Object.keys(langs).forEach((ns) => { + i18next.addResourceBundle(currentLang, ns, langs[ns]) + }) + }, + fallbackLng, + cache: { + querystring: lookupTarget, + htmlTag: true, + }, +}) + +const _changeLanguage = i18next.changeLanguage +i18next.changeLanguage = async (lang: string, ...args) => { + const currentLng = lang + + // 语言改变之前,先加载资源 + await loadResourceByLang(currentLng) + return _changeLanguage(currentLng, ...args) +} diff --git a/playground/vscode-setting/src/vite-env.d.ts b/playground/vscode-setting/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/playground/vscode-setting/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/playground/vscode-setting/vite.config.ts b/playground/vscode-setting/vite.config.ts new file mode 100644 index 0000000..6685026 --- /dev/null +++ b/playground/vscode-setting/vite.config.ts @@ -0,0 +1,14 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' +import { i18nDetector } from 'vite-plugin-i18n-detector' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + react(), + i18nDetector({ + root: __dirname, + dotVscodePath: __dirname, + }), + ], +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c56651..1649765 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,7 +37,7 @@ importers: version: 1.0.1 vite: specifier: '>=4.0.0' - version: 4.4.11(@types/node@18.13.0) + version: 4.4.11(@types/node@20.10.4) watcher: specifier: ^2.3.0 version: 2.3.0 @@ -66,6 +66,9 @@ importers: '@types/language-tags': specifier: ^1.0.4 version: 1.0.4 + '@types/node': + specifier: ^20.10.4 + version: 20.10.4 '@types/react': specifier: ^18.2.45 version: 18.2.45 @@ -171,7 +174,47 @@ importers: version: 5.3.3 vite: specifier: ^4.4.11 - version: 4.4.11(@types/node@18.13.0) + version: 4.4.11(@types/node@20.10.4) + + playground/vscode-setting: + dependencies: + i18next: + specifier: ^23.5.1 + version: 23.5.1 + i18next-browser-languagedetector: + specifier: ^7.1.0 + version: 7.2.0 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + react-i18next: + specifier: ^13.2.2 + version: 13.2.2(i18next@23.5.1)(react-dom@18.2.0)(react@18.2.0) + vite-plugin-i18n-detector: + specifier: workspace:* + version: link:../.. + devDependencies: + '@types/react': + specifier: ^18.2.28 + version: 18.2.45 + '@types/react-dom': + specifier: ^18.2.13 + version: 18.2.17 + '@vitejs/plugin-react': + specifier: ^4.1.0 + version: 4.2.1(vite@4.4.11) + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + typescript: + specifier: ^5.2.2 + version: 5.3.3 + vite: + specifier: ^4.4.11 + version: 4.4.11(@types/node@20.10.4) packages: @@ -1286,6 +1329,12 @@ packages: /@types/node@18.13.0: resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==} + dev: true + + /@types/node@20.10.4: + resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==} + dependencies: + undici-types: 5.26.5 /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -1467,7 +1516,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.6) '@types/babel__core': 7.20.5 react-refresh: 0.14.0 - vite: 4.4.11(@types/node@18.13.0) + vite: 4.4.11(@types/node@20.10.4) transitivePeerDependencies: - supports-color dev: true @@ -3932,7 +3981,6 @@ packages: resolution: {integrity: sha512-JelYzcaCoFDaa+Ysbfz2JsGAKkrHiMG6S61+HLBUEIPaF40WMwW9hCPymlQGrP+wWawKxKPuSuD71WZscCsWHg==} dependencies: '@babel/runtime': 7.23.2 - dev: true /i18next@23.7.9: resolution: {integrity: sha512-wturtxTfJLJdLzHhyfxXo2l9Cbu2Iz4wF4065oWThPvdFJMUUG3fhXD3BLCHgrv4VxfScORq0i9sfCdjVPbgiw==} @@ -5751,7 +5799,6 @@ packages: i18next: 23.5.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /react-i18next@13.5.0(i18next@23.7.9)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA==} @@ -6815,6 +6862,9 @@ packages: mlly: 1.4.2 dev: true + /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'} @@ -6915,7 +6965,7 @@ packages: builtins: 5.0.1 dev: true - /vite-node@0.34.5(@types/node@18.13.0): + /vite-node@0.34.5(@types/node@20.10.4): resolution: {integrity: sha512-RNZ+DwbCvDoI5CbCSQSyRyzDTfFvFauvMs6Yq4ObJROKlIKuat1KgSX/Ako5rlDMfVCyMcpMRMTkJBxd6z8YRA==} engines: {node: '>=v14.18.0'} hasBin: true @@ -6925,7 +6975,7 @@ packages: mlly: 1.4.2 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.5.0(@types/node@18.13.0) + vite: 4.5.0(@types/node@20.10.4) transitivePeerDependencies: - '@types/node' - less @@ -6937,7 +6987,7 @@ packages: - terser dev: true - /vite@4.4.11(@types/node@18.13.0): + /vite@4.4.11(@types/node@20.10.4): resolution: {integrity: sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -6965,14 +7015,14 @@ packages: terser: optional: true dependencies: - '@types/node': 18.13.0 + '@types/node': 20.10.4 esbuild: 0.18.20 postcss: 8.4.30 rollup: 3.29.3 optionalDependencies: fsevents: 2.3.2 - /vite@4.5.0(@types/node@18.13.0): + /vite@4.5.0(@types/node@20.10.4): resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -7000,7 +7050,7 @@ packages: terser: optional: true dependencies: - '@types/node': 18.13.0 + '@types/node': 20.10.4 esbuild: 0.18.20 postcss: 8.4.30 rollup: 3.29.3 @@ -7020,7 +7070,7 @@ packages: fs-extra: 11.2.0 kill-port: 1.6.1 playwright-chromium: 1.39.0 - vite: 4.4.11(@types/node@18.13.0) + vite: 4.4.11(@types/node@20.10.4) vitest: 0.34.5(jsdom@22.1.0) dev: true @@ -7057,7 +7107,7 @@ packages: dependencies: '@types/chai': 4.3.9 '@types/chai-subset': 1.3.3 - '@types/node': 18.13.0 + '@types/node': 20.10.4 '@vitest/expect': 0.34.5 '@vitest/runner': 0.34.5 '@vitest/snapshot': 0.34.5 @@ -7077,8 +7127,8 @@ packages: strip-literal: 1.0.1 tinybench: 2.5.1 tinypool: 0.7.0 - vite: 4.5.0(@types/node@18.13.0) - vite-node: 0.34.5(@types/node@18.13.0) + vite: 4.5.0(@types/node@20.10.4) + vite-node: 0.34.5(@types/node@20.10.4) why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/src/plugin/index.ts b/src/plugin/index.ts index 63544ec..818631a 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -20,9 +20,11 @@ export interface I18nDetectorOptions { */ localesPaths?: string[] /** - * @default false + * @description localesPaths's root path + * localesPaths are relative to root + * @default process.cwd() */ - namespace?: boolean + root?: string /** * @description rule of matching locale file * @@ -54,10 +56,20 @@ export interface I18nDetectorOptions { */ parserPlugins?: ParserPlugin[] /** - * @description root path + * @default false + */ + namespace?: boolean + /** + * @description i18n-ally config root path * @default process.cwd() + * + * if dotVscodePath is process.cwd() + * i18n-ally config path is + * path.resolve(process.cwd(), './vscode/settings.json') by default + * + * if false, will not detect i18n-ally config */ - root?: string + dotVscodePath?: string | false } export async function i18nDetector(opts?: I18nDetectorOptions): Promise { @@ -162,9 +174,7 @@ export async function i18nDetector(opts?: I18nDetectorOptions): Promise { const updated = await localeDetector.onFileChanged({ fsPath: file }) if (updated) { - const { resolvedIds } = localeDetector.localeModules - - debug('hmr', resolvedIds) + debug('hmr', file) hmr(server, localeDetector) } }, diff --git a/src/plugin/locale-detector/LocaleDetector.ts b/src/plugin/locale-detector/LocaleDetector.ts index 7b98211..a559d42 100644 --- a/src/plugin/locale-detector/LocaleDetector.ts +++ b/src/plugin/locale-detector/LocaleDetector.ts @@ -13,9 +13,7 @@ import { PKGNAME, VIRTUAL } from '../utils/constant' import { debug } from '../utils/debugger' import { logger } from '../utils/logger' -export interface Config extends Required { - root: string -} +export type Config = Omit, 'dotVscodePath'> type PathMatcherType = RegExp @@ -63,10 +61,10 @@ export class LocaleDetector { } this._localesPaths = c.localesPaths.map((item) => - trimEnd(normalizePath(path.isAbsolute(item) ? item : path.resolve(this._rootPath, item)), '/\\').replaceAll( - '\\', - '/', - ), + trimEnd( + normalizePath(path.posix.isAbsolute(item) ? item : path.posix.resolve(this._rootPath, item)), + '/\\', + ).replaceAll('\\', '/'), ) } @@ -384,7 +382,7 @@ export class LocaleDetector { } } if (this._localeDirs.length === 0) { - logger.error('\n⚠ No locales paths.') + logger.error('\n❌ No locales paths.') return false } diff --git a/src/plugin/utils/hmr.ts b/src/plugin/utils/hmr.ts index ef30d6a..e764d1a 100644 --- a/src/plugin/utils/hmr.ts +++ b/src/plugin/utils/hmr.ts @@ -1,14 +1,12 @@ import { type ViteDevServer } from 'vite' import { type LocaleDetector } from '../locale-detector/LocaleDetector' import { RESOLVED_VIRTUAL_PREFIX } from './constant' -import { debug } from './debugger' export function hmr(server: ViteDevServer, localeDetector: LocaleDetector) { const { resolvedIds } = localeDetector.localeModules for (const [, value] of resolvedIds) { const { moduleGraph, ws } = server const module = moduleGraph.getModuleById(RESOLVED_VIRTUAL_PREFIX + value) - debug('module:', module) if (module) { moduleGraph.invalidateModule(module) diff --git a/src/plugin/utils/init-options.ts b/src/plugin/utils/init-options.ts index efce302..617ba65 100644 --- a/src/plugin/utils/init-options.ts +++ b/src/plugin/utils/init-options.ts @@ -1,47 +1,34 @@ -import path from 'node:path' import { type I18nDetectorOptions } from '..' import { getI18nAllyConfigByKey, readI18nAllyConfig } from './vscode-settings' -const DEFAULT_OPTIONS: Required = { +const DEFAULT_OPTIONS: I18nDetectorOptions = { localesPaths: ['./src/locales', './locales'], - pathMatcher: '', - parserPlugins: [], root: process.cwd(), namespace: false, + dotVscodePath: process.cwd(), } -function getDefaultOptions(root: string): Required { - const i18AllyConfig = readI18nAllyConfig(root) - if (i18AllyConfig) { - let localesPaths = getI18nAllyConfigByKey(i18AllyConfig, 'localesPaths') - - if (Array.isArray(localesPaths)) { - localesPaths = localesPaths.map((p) => { - if (process.env.E2E) { - p = p.replace('playground', 'playground-temp') - } - return path.resolve(i18AllyConfig.i18nRootPath, p) - }) - } +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 (i18AllyConfig) { return { - localesPaths: localesPaths ?? DEFAULT_OPTIONS.localesPaths, - pathMatcher: getI18nAllyConfigByKey(i18AllyConfig, 'pathMatcher') ?? DEFAULT_OPTIONS.pathMatcher, + localesPaths: getI18nAllyConfigByKey(i18AllyConfig, 'localesPaths') ?? DEFAULT_OPTIONS.localesPaths, + pathMatcher: getI18nAllyConfigByKey(i18AllyConfig, 'pathMatcher'), namespace: getI18nAllyConfigByKey(i18AllyConfig, 'namespace') ?? DEFAULT_OPTIONS.namespace, - root: DEFAULT_OPTIONS.root, - parserPlugins: DEFAULT_OPTIONS.parserPlugins, + ...DEFAULT_OPTIONS, } } return DEFAULT_OPTIONS } export function initOptions(options?: I18nDetectorOptions) { - if (!options) { - return getDefaultOptions(DEFAULT_OPTIONS.root) - } - return { - ...getDefaultOptions(options.root ?? DEFAULT_OPTIONS.root), + ...getDefaultOptions(options), ...options, } as Required } diff --git a/src/plugin/utils/vscode-settings.ts b/src/plugin/utils/vscode-settings.ts index b07ddf2..9336d1a 100644 --- a/src/plugin/utils/vscode-settings.ts +++ b/src/plugin/utils/vscode-settings.ts @@ -1,9 +1,11 @@ import { findUpSync } from 'find-up' import JSON5 from 'json5' import fs from 'node:fs' +import path from 'node:path' -const SETTING_FILE = '.vscode/settings.json' +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', @@ -24,24 +26,27 @@ export function readFile(filePath?: string) { export const I18N_ALLY_KEY = 'i18n-ally.' -export function readI18nAllyConfig(cwd?: string) { - const filePath = findupVscodeSettings(cwd) - const settings = readFile(filePath) +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) => { - obj[key] = settings[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, - i18nRootPath: filePath?.replace(new RegExp(`${SETTING_FILE}$`), '') ?? '', - } + return filteredConfig } } return undefined