feat: add modular i18n foundation

This commit is contained in:
duanshuwen
2026-05-26 14:37:32 +08:00
parent a9b00627e2
commit b05d5a72cd
24 changed files with 606 additions and 4 deletions

View File

@@ -0,0 +1,190 @@
# I18n Modules Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add maintainable feature-module internationalization for Chinese, English, and Thai.
**Architecture:** Use `vue-i18n` as the app i18n runtime and keep message ownership under `src/i18n/modules/<feature>/<locale>.ts`. Locale resolution, persistence, document language, and Vant locale synchronization are centralized behind `resolveInitialLocale` and `setLocale`.
**Tech Stack:** Vue 3.4, Vite 5, Vant 4.9, Yarn 1.22, Node test runner, TypeScript.
---
## File Structure
- Create: `src/i18n/types.ts` for locale constants, locale types, and type guards.
- Create: `src/i18n/storage.ts` for safe browser storage reads and writes.
- Create: `src/i18n/locales.ts` for initial locale resolution and browser language mapping.
- Create: `src/i18n/vant.ts` for mapping app locales to Vant locale packs.
- Create: `src/i18n/index.ts` for `vue-i18n` creation, message aggregation, and `setLocale`.
- Create: `src/i18n/modules/common/{zh-CN,en-US,th-TH,index}.ts`.
- Create: `src/i18n/modules/home/{zh-CN,en-US,th-TH,index}.ts`.
- Create: `src/i18n/modules/quick/{zh-CN,en-US,th-TH,index}.ts`.
- Create: `src/i18n/messages.ts` for top-level message aggregation.
- Create: `src/i18n/i18n.test.ts` for locale and message consistency tests.
- Modify: `src/main.ts` to install the i18n plugin.
- Modify: `package.json` and `yarn.lock` by adding `vue-i18n@^11.4.4`.
## Task 1: Add Locale Logic Tests
**Files:**
- Create: `src/i18n/i18n.test.ts`
- [ ] **Step 1: Write failing tests for locale resolution and message key consistency**
```ts
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { defaultLocale, supportedLocales } from "./types.ts";
import { isSupportedLocale, resolveInitialLocale, resolveLocaleFromNavigator } from "./locales.ts";
import { messages } from "./messages.ts";
function flattenKeys(value: unknown, prefix = ""): string[] {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return [prefix];
}
return Object.entries(value as Record<string, unknown>).flatMap(([key, nestedValue]) =>
flattenKeys(nestedValue, prefix ? `${prefix}.${key}` : key),
);
}
describe("i18n locale model", () => {
it("supports Chinese, English, and Thai", () => {
assert.deepEqual([...supportedLocales], ["zh-CN", "en-US", "th-TH"]);
assert.equal(defaultLocale, "zh-CN");
});
it("accepts only supported locale codes", () => {
assert.equal(isSupportedLocale("zh-CN"), true);
assert.equal(isSupportedLocale("en-US"), true);
assert.equal(isSupportedLocale("th-TH"), true);
assert.equal(isSupportedLocale("en"), false);
assert.equal(isSupportedLocale("fr-FR"), false);
});
it("maps browser languages to supported locales", () => {
assert.equal(resolveLocaleFromNavigator(["zh-Hans-CN"]), "zh-CN");
assert.equal(resolveLocaleFromNavigator(["en-GB"]), "en-US");
assert.equal(resolveLocaleFromNavigator(["th"]), "th-TH");
assert.equal(resolveLocaleFromNavigator(["fr-FR"]), defaultLocale);
});
it("prefers stored locale over browser language", () => {
assert.equal(resolveInitialLocale({ storedLocale: "th-TH", navigatorLanguages: ["en-US"] }), "th-TH");
});
it("falls back to browser language when stored locale is invalid", () => {
assert.equal(resolveInitialLocale({ storedLocale: "invalid", navigatorLanguages: ["en-US"] }), "en-US");
});
it("keeps translation key structure consistent across locales", () => {
const referenceKeys = flattenKeys(messages["zh-CN"]).sort();
for (const locale of supportedLocales) {
assert.deepEqual(flattenKeys(messages[locale]).sort(), referenceKeys, locale);
}
});
});
```
- [ ] **Step 2: Run tests and verify the expected failure**
Run: `corepack yarn test`
Expected: FAIL because `src/i18n/types.ts`, `src/i18n/locales.ts`, and `src/i18n/messages.ts` do not exist yet.
## Task 2: Implement Locale Model and Message Modules
**Files:**
- Create: all `src/i18n` files except `index.ts` and `vant.ts`
- [ ] **Step 1: Implement locale constants and resolution**
Add `supportedLocales`, `defaultLocale`, `isSupportedLocale`, `resolveLocaleFromNavigator`, and `resolveInitialLocale` exactly as tested.
- [ ] **Step 2: Implement safe storage helpers**
Add `localeStorageKey`, `readStoredLocale`, and `writeStoredLocale` so unavailable browser storage never blocks startup.
Cover storage getter failures with a regression test before final verification.
- [ ] **Step 3: Implement message modules**
Create `common`, `home`, and `quick` modules with matching key shapes for `zh-CN`, `en-US`, and `th-TH`.
- [ ] **Step 4: Run tests and verify locale tests pass**
Run: `corepack yarn test`
Expected: PASS for locale model and message key consistency.
## Task 3: Install and Wire Runtime I18n
**Files:**
- Modify: `package.json`
- Modify: `yarn.lock`
- Create: `src/i18n/vant.ts`
- Create: `src/i18n/index.ts`
- Modify: `src/main.ts`
- [ ] **Step 1: Add `vue-i18n` dependency**
Run: `corepack yarn add vue-i18n@^11.4.4`
Expected: `package.json` and `yarn.lock` include `vue-i18n`.
- [ ] **Step 2: Implement Vant locale synchronization**
Use Vant's `Locale.use(locale, messages)` API with local language packs from `vant/es/locale/lang/*.mjs`.
- [ ] **Step 3: Create the i18n plugin**
Use `createI18n({ legacy: false, locale, fallbackLocale: defaultLocale, messages })`, export `i18n`, `setLocale`, and `getCurrentLocale`.
- [ ] **Step 4: Register i18n in the app**
Modify `src/main.ts` to call `.use(i18n)` before mounting.
- [ ] **Step 5: Run typecheck**
Run: `corepack yarn typecheck`
Expected: PASS with no TypeScript errors.
## Task 4: Final Verification and Commit
**Files:**
- Verify all changed files.
- [ ] **Step 1: Run unit tests**
Run: `corepack yarn test`
Expected: PASS.
- [ ] **Step 2: Run production build**
Run: `corepack yarn build`
Expected: PASS.
- [ ] **Step 3: Review diff for scope**
Run: `git status --short` and `git diff -- src package.json yarn.lock docs/superpowers/plans/2026-05-26-i18n-modules.md`.
Expected: Only i18n implementation files, dependency metadata, the approved plan, and pre-existing user changes appear.
- [ ] **Step 4: Commit only implementation-owned files**
Stage:
```bash
git add -f docs/superpowers/plans/2026-05-26-i18n-modules.md
git add package.json yarn.lock src/main.ts src/i18n
```
Commit:
```bash
git commit -m "feat: add modular i18n foundation"
```

View File

@@ -8,6 +8,6 @@
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/app/main.ts"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -17,6 +17,7 @@
"pinia": "^3.0.3",
"vant": "^4.9.24",
"vue": "^3.4.21",
"vue-i18n": "11.4.4",
"vue-router": "^4.5.1"
},
"devDependencies": {

76
src/i18n/i18n.test.ts Normal file
View File

@@ -0,0 +1,76 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { isSupportedLocale, resolveInitialLocale, resolveLocaleFromNavigator } from "./locales.ts";
import { messages } from "./messages.ts";
import { readStoredLocale, writeStoredLocale } from "./storage.ts";
import { defaultLocale, supportedLocales } from "./types.ts";
function flattenKeys(value: unknown, prefix = ""): string[] {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return [prefix];
}
return Object.entries(value as Record<string, unknown>).flatMap(([key, nestedValue]) =>
flattenKeys(nestedValue, prefix ? `${prefix}.${key}` : key),
);
}
describe("i18n locale model", () => {
it("supports Chinese, English, and Thai", () => {
assert.deepEqual([...supportedLocales], ["zh-CN", "en-US", "th-TH"]);
assert.equal(defaultLocale, "zh-CN");
});
it("accepts only supported locale codes", () => {
assert.equal(isSupportedLocale("zh-CN"), true);
assert.equal(isSupportedLocale("en-US"), true);
assert.equal(isSupportedLocale("th-TH"), true);
assert.equal(isSupportedLocale("en"), false);
assert.equal(isSupportedLocale("fr-FR"), false);
});
it("maps browser languages to supported locales", () => {
assert.equal(resolveLocaleFromNavigator(["zh-Hans-CN"]), "zh-CN");
assert.equal(resolveLocaleFromNavigator(["en-GB"]), "en-US");
assert.equal(resolveLocaleFromNavigator(["th"]), "th-TH");
assert.equal(resolveLocaleFromNavigator(["fr-FR"]), defaultLocale);
});
it("prefers stored locale over browser language", () => {
assert.equal(resolveInitialLocale({ storedLocale: "th-TH", navigatorLanguages: ["en-US"] }), "th-TH");
});
it("falls back to browser language when stored locale is invalid", () => {
assert.equal(resolveInitialLocale({ storedLocale: "invalid", navigatorLanguages: ["en-US"] }), "en-US");
});
it("keeps translation key structure consistent across locales", () => {
const referenceKeys = flattenKeys(messages["zh-CN"]).sort();
for (const locale of supportedLocales) {
assert.deepEqual(flattenKeys(messages[locale]).sort(), referenceKeys, locale);
}
});
it("does not throw when browser storage is unavailable", () => {
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
Object.defineProperty(globalThis, "localStorage", {
configurable: true,
get() {
throw new Error("storage blocked");
},
});
try {
assert.equal(readStoredLocale(), null);
assert.doesNotThrow(() => writeStoredLocale("zh-CN"));
} finally {
if (descriptor) {
Object.defineProperty(globalThis, "localStorage", descriptor);
} else {
Reflect.deleteProperty(globalThis, "localStorage");
}
}
});
});

60
src/i18n/index.ts Normal file
View File

@@ -0,0 +1,60 @@
import { createI18n } from "vue-i18n";
import { isSupportedLocale, resolveInitialLocale } from "./locales.ts";
import { messages } from "./messages.ts";
import { readStoredLocale, writeStoredLocale } from "./storage.ts";
import { defaultLocale } from "./types.ts";
import type { SupportedLocale } from "./types.ts";
import { syncVantLocale } from "./vant.ts";
function getNavigatorLanguages(): string[] {
if (typeof navigator === "undefined") {
return [];
}
const languages = navigator.languages ? [...navigator.languages] : [];
if (languages.length > 0) {
return languages;
}
return navigator.language ? [navigator.language] : [];
}
function setDocumentLanguage(locale: SupportedLocale): void {
if (typeof document === "undefined") {
return;
}
document.documentElement.lang = locale;
}
const initialLocale = resolveInitialLocale({
storedLocale: readStoredLocale(),
navigatorLanguages: getNavigatorLanguages(),
});
syncVantLocale(initialLocale);
setDocumentLanguage(initialLocale);
export const i18n = createI18n({
legacy: false,
locale: initialLocale,
fallbackLocale: defaultLocale,
messages,
});
export function getCurrentLocale(): SupportedLocale {
const locale = i18n.global.locale.value;
return isSupportedLocale(locale) ? locale : defaultLocale;
}
export function setLocale(locale: string): void {
if (!isSupportedLocale(locale)) {
return;
}
i18n.global.locale.value = locale;
writeStoredLocale(locale);
syncVantLocale(locale);
setDocumentLanguage(locale);
}

39
src/i18n/locales.ts Normal file
View File

@@ -0,0 +1,39 @@
import { defaultLocale, supportedLocales } from "./types.ts";
import type { SupportedLocale } from "./types.ts";
export function isSupportedLocale(locale: string | null | undefined): locale is SupportedLocale {
return supportedLocales.includes(locale as SupportedLocale);
}
export function resolveLocaleFromNavigator(languages: readonly string[] = []): SupportedLocale {
for (const language of languages) {
const normalizedLanguage = language.toLowerCase();
if (normalizedLanguage.startsWith("zh")) {
return "zh-CN";
}
if (normalizedLanguage.startsWith("en")) {
return "en-US";
}
if (normalizedLanguage.startsWith("th")) {
return "th-TH";
}
}
return defaultLocale;
}
type ResolveInitialLocaleOptions = {
storedLocale?: string | null;
navigatorLanguages?: readonly string[];
};
export function resolveInitialLocale(options: ResolveInitialLocaleOptions = {}): SupportedLocale {
if (isSupportedLocale(options.storedLocale)) {
return options.storedLocale;
}
return resolveLocaleFromNavigator(options.navigatorLanguages);
}

25
src/i18n/messages.ts Normal file
View File

@@ -0,0 +1,25 @@
import { supportedLocales } from "./types.ts";
import type { SupportedLocale } from "./types.ts";
import { commonMessages } from "./modules/common/index.ts";
import type { CommonMessages } from "./modules/common/index.ts";
import { homeMessages } from "./modules/home/index.ts";
import type { HomeMessages } from "./modules/home/index.ts";
import { quickMessages } from "./modules/quick/index.ts";
import type { QuickMessages } from "./modules/quick/index.ts";
export type AppMessages = {
common: CommonMessages;
home: HomeMessages;
quick: QuickMessages;
};
export const messages = Object.fromEntries(
supportedLocales.map((locale) => [
locale,
{
common: commonMessages[locale],
home: homeMessages[locale],
quick: quickMessages[locale],
},
]),
) as Record<SupportedLocale, AppMessages>;

View File

@@ -0,0 +1,14 @@
export default {
actions: {
cancel: "Cancel",
confirm: "Confirm",
retry: "Retry",
},
errors: {
network: "Network error. Please try again later.",
},
state: {
empty: "No data",
loading: "Loading...",
},
} as const;

View File

@@ -0,0 +1,12 @@
import type { LocaleMessageSchema, SupportedLocale } from "../../types.ts";
import enUS from "./en-US.ts";
import thTH from "./th-TH.ts";
import zhCN from "./zh-CN.ts";
export type CommonMessages = LocaleMessageSchema<typeof zhCN>;
export const commonMessages = {
"zh-CN": zhCN,
"en-US": enUS,
"th-TH": thTH,
} satisfies Record<SupportedLocale, CommonMessages>;

View File

@@ -0,0 +1,14 @@
export default {
actions: {
cancel: "ยกเลิก",
confirm: "ยืนยัน",
retry: "ลองอีกครั้ง",
},
errors: {
network: "เครือข่ายผิดพลาด โปรดลองอีกครั้งในภายหลัง",
},
state: {
empty: "ไม่มีข้อมูล",
loading: "กำลังโหลด...",
},
} as const;

View File

@@ -0,0 +1,14 @@
export default {
actions: {
cancel: "取消",
confirm: "确认",
retry: "重试",
},
errors: {
network: "网络异常,请稍后重试",
},
state: {
empty: "暂无数据",
loading: "加载中...",
},
} as const;

View File

@@ -0,0 +1,4 @@
export default {
subtitle: "Welcome to the mobile app",
title: "Home",
} as const;

View File

@@ -0,0 +1,12 @@
import type { LocaleMessageSchema, SupportedLocale } from "../../types.ts";
import enUS from "./en-US.ts";
import thTH from "./th-TH.ts";
import zhCN from "./zh-CN.ts";
export type HomeMessages = LocaleMessageSchema<typeof zhCN>;
export const homeMessages = {
"zh-CN": zhCN,
"en-US": enUS,
"th-TH": thTH,
} satisfies Record<SupportedLocale, HomeMessages>;

View File

@@ -0,0 +1,4 @@
export default {
subtitle: "ยินดีต้อนรับสู่แอปมือถือ",
title: "หน้าแรก",
} as const;

View File

@@ -0,0 +1,4 @@
export default {
subtitle: "欢迎使用移动端应用",
title: "首页",
} as const;

View File

@@ -0,0 +1,7 @@
export default {
form: {
placeholder: "Enter content",
submit: "Submit",
},
title: "Quick Actions",
} as const;

View File

@@ -0,0 +1,12 @@
import type { LocaleMessageSchema, SupportedLocale } from "../../types.ts";
import enUS from "./en-US.ts";
import thTH from "./th-TH.ts";
import zhCN from "./zh-CN.ts";
export type QuickMessages = LocaleMessageSchema<typeof zhCN>;
export const quickMessages = {
"zh-CN": zhCN,
"en-US": enUS,
"th-TH": thTH,
} satisfies Record<SupportedLocale, QuickMessages>;

View File

@@ -0,0 +1,7 @@
export default {
form: {
placeholder: "กรอกเนื้อหา",
submit: "ส่ง",
},
title: "การดำเนินการด่วน",
} as const;

View File

@@ -0,0 +1,7 @@
export default {
form: {
placeholder: "请输入内容",
submit: "提交",
},
title: "快捷操作",
} as const;

35
src/i18n/storage.ts Normal file
View File

@@ -0,0 +1,35 @@
import { isSupportedLocale } from "./locales.ts";
import type { SupportedLocale } from "./types.ts";
export const localeStorageKey = "nianxx.locale";
type LocaleStorageLike = Pick<Storage, "getItem" | "setItem">;
function resolveStorage(storage?: LocaleStorageLike): LocaleStorageLike | undefined {
if (storage) {
return storage;
}
try {
return globalThis.localStorage;
} catch {
return undefined;
}
}
export function readStoredLocale(storage?: LocaleStorageLike): SupportedLocale | null {
try {
const locale = resolveStorage(storage)?.getItem(localeStorageKey);
return isSupportedLocale(locale) ? locale : null;
} catch {
return null;
}
}
export function writeStoredLocale(locale: SupportedLocale, storage?: LocaleStorageLike): void {
try {
resolveStorage(storage)?.setItem(localeStorageKey, locale);
} catch {
// Storage can be unavailable in private or embedded browser contexts.
}
}

9
src/i18n/types.ts Normal file
View File

@@ -0,0 +1,9 @@
export const supportedLocales = ["zh-CN", "en-US", "th-TH"] as const;
export type SupportedLocale = (typeof supportedLocales)[number];
export const defaultLocale: SupportedLocale = "zh-CN";
export type LocaleMessageSchema<T> = {
readonly [Key in keyof T]: T[Key] extends string ? string : LocaleMessageSchema<T[Key]>;
};

15
src/i18n/vant.ts Normal file
View File

@@ -0,0 +1,15 @@
import { Locale } from "vant";
import enUS from "vant/es/locale/lang/en-US";
import thTH from "vant/es/locale/lang/th-TH";
import zhCN from "vant/es/locale/lang/zh-CN";
import type { SupportedLocale } from "./types.ts";
const vantMessages = {
"zh-CN": zhCN,
"en-US": enUS,
"th-TH": thTH,
} satisfies Record<SupportedLocale, Record<string, unknown>>;
export function syncVantLocale(locale: SupportedLocale): void {
Locale.use(locale, vantMessages[locale]);
}

View File

@@ -1,8 +1,9 @@
import { createApp } from "vue";
import App from "./App.vue";
import { i18n } from "./i18n";
import { pinia } from "./stores";
import { router } from "./router";
import "vant/lib/index.css";
import "@/styles/main.css";
createApp(App).use(pinia).use(router).mount("#app");
createApp(App).use(pinia).use(i18n).use(router).mount("#app");

View File

@@ -164,6 +164,36 @@
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc"
integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
"@intlify/core-base@11.4.4":
version "11.4.4"
resolved "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.4.4.tgz#ea20ab23eae5e96657fdd006e6b7fd57851fbbc8"
integrity sha512-w/vItlylrAmhebkIbVl5YY8XMCtj8Mb2g70ttxktMYuf5AuRahgEHL2iLgLIsZBIbTSgs4hkUo7ucCL0uTJvOg==
dependencies:
"@intlify/devtools-types" "11.4.4"
"@intlify/message-compiler" "11.4.4"
"@intlify/shared" "11.4.4"
"@intlify/devtools-types@11.4.4":
version "11.4.4"
resolved "https://registry.npmmirror.com/@intlify/devtools-types/-/devtools-types-11.4.4.tgz#a64d9e135c04c5f00e430fff86ca5467e8fbd9cf"
integrity sha512-PcBLmGmDQsTSVV911P8upzpcLJO1CNVYi/IH6bGnLR2nA+0L963+kXN1ZrisTEnbtw2ewN6HMMSldqzjronA0Q==
dependencies:
"@intlify/core-base" "11.4.4"
"@intlify/shared" "11.4.4"
"@intlify/message-compiler@11.4.4":
version "11.4.4"
resolved "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.4.4.tgz#946d5de62a9129a5009382def22cc4e0e2532df9"
integrity sha512-vn0OAV9pYkJlPPmgnsSm5eAG3mL0+9C/oaded2JY9jmxBbhmUXT3TcAUY8WRgLY9Hte7lkUJKpXrVlYjMXBD2w==
dependencies:
"@intlify/shared" "11.4.4"
source-map-js "^1.0.2"
"@intlify/shared@11.4.4":
version "11.4.4"
resolved "https://registry.npmmirror.com/@intlify/shared/-/shared-11.4.4.tgz#4ccb2816bd0a41de288048f87bd946a8d629e906"
integrity sha512-QRUCHqda1U6aR14FR0vvXD4+4gj6+fm0AhAozvSuRCw0fCvrmCugWpgiR4xH2NI6s8am6N9p5OhirplsX8ZS3g==
"@jridgewell/gen-mapping@^0.3.5":
version "0.3.13"
resolved "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
@@ -547,7 +577,7 @@
de-indent "^1.0.2"
he "^1.2.0"
"@vue/devtools-api@^6.6.4":
"@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.4":
version "6.6.4"
resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343"
integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==
@@ -1119,7 +1149,7 @@ rollup@^4.13.0:
"@rollup/rollup-win32-x64-msvc" "4.60.4"
fsevents "~2.3.2"
source-map-js@^1.2.1:
source-map-js@^1.0.2, source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
@@ -1186,6 +1216,16 @@ vscode-uri@^3.0.8:
resolved "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c"
integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==
vue-i18n@11.4.4:
version "11.4.4"
resolved "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.4.4.tgz#92de5050e582074168f115458358a123b915debd"
integrity sha512-gIbXVSFQV4jcSJxfwdZ5zSZmZ+12CnX0K3vBkRSd6Zn+HSzCp+QwUgPwpD/uN0oKNKI9RzlUXPKVedEuMgNG0A==
dependencies:
"@intlify/core-base" "11.4.4"
"@intlify/devtools-types" "11.4.4"
"@intlify/shared" "11.4.4"
"@vue/devtools-api" "^6.5.0"
vue-router@^4.5.1:
version "4.6.4"
resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz#a0a9cb9ef811a106d249e4bb9313d286718020d8"