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