diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3b54caa10..8fe61158e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -43,6 +43,10 @@ WolfStar.rocks is built for Discord server administrators and bot users who need - [Lighthouse accessibility tests](#lighthouse-accessibility-tests) - [Lighthouse performance tests](#lighthouse-performance-tests) - [End-to-end tests](#end-to-end-tests) +- [Localization (i18n)](#localization-i18n) + - [i18n commands](#i18n-commands) + - [Adding a locale](#adding-a-locale) + - [Translation status (Lunaria)](#translation-status-lunaria) - [Storybook](#storybook) - [Submitting changes](#submitting-changes) - [Before submitting](#before-submitting) @@ -143,6 +147,14 @@ pnpm prisma:push # Push schema changes (development) pnpm prisma:migrate:dev # Create and apply migration pnpm prisma:generate # Regenerate Prisma client pnpm prisma:studio # Visual database editor (http://localhost:5555) + +# Localization (vp tasks + fix scripts) +pnpm vp run i18n:check # Audit locale files against en.json +pnpm i18n:check:fix # Add missing keys (EN placeholders) / remove extras +pnpm vp run i18n:report # Detect missing, unused, or dynamic keys in code +pnpm i18n:report:fix # Remove unused keys from all locale files +pnpm vp run i18n:schema # Regenerate i18n/schema.json from en.json +pnpm vp run build:lunaria # Build /lunaria dashboard + status.json ``` ### GitHub Actions security analysis @@ -383,6 +395,47 @@ pnpm test:browser:ui # Run with Playwright UI Make sure to read about [Playwright best practices](https://playwright.dev/docs/best-practices) and prefer user-facing locators (`getByRole`, `getByLabel`, `getByText`) over selectors based on classes or IDs. +## Localization (i18n) + +WolfStar.rocks uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for the dashboard UI. Guild bot-response languages (`en-US`, `es-ES`, …) are separate from UI locale preference. + +- Source of truth: [`i18n/locales/en.json`](../i18n/locales/en.json) +- Other locales: [`i18n/locales/`](../i18n/locales) (currently `es-ES`, matching WolfStar/Skyra language keys) +- Locale registry: [`config/i18n.ts`](../config/i18n.ts) +- Lunaria config: [`lunaria.config.json`](../lunaria.config.json) + +### i18n commands + +| Command | Purpose | +| ------------------------------ | ------------------------------------------------------------------------- | +| `pnpm vp run i18n:check` | Compare locales to `en.json` (reports missing/extra keys; removes extras) | +| `pnpm i18n:check:fix [locale]` | Add missing keys with English placeholders (optionally for one locale) | +| `pnpm vp run i18n:report` | Fail on missing, unused, or dynamic keys used in `app/**` | +| `pnpm i18n:report:fix` | Remove unused keys from all locale files | +| `pnpm vp run i18n:schema` | Regenerate `i18n/schema.json` for IDE validation | +| `pnpm vp run build:lunaria` | Build `dist/lunaria/` dashboard + `status.json` | + +CI runs `i18n:report` and checks that `i18n/schema.json` is up to date. Autofix runs `i18n:check` and `build:lunaria`. The Lunaria PR workflow posts a translation overview comment. + +### Adding a locale + +1. Create `i18n/locales/.json` (start from `en.json`) +2. Register it in `config/i18n.ts` (`locales` array) +3. Add it to `lunaria.config.json` → `locales` +4. Run `pnpm i18n:check:fix` and `pnpm vp run i18n:schema` + +Prefer static string keys with `$t('…')` / `t('…')` so `i18n:report` can analyze usage. For HTML inside translations, use [`i18n-t`](https://vue-i18n.intlify.dev/guide/advanced/component.html). + +### Translation status (Lunaria) + +We track translation progress with [Lunaria](https://lunaria.dev/): + +- Built dashboard: `/lunaria/` (generated at build time) +- JSON status for the app: `/lunaria/status.json` +- In-app page: `/translation-status` + +Use the [i18n-ally](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) VS Code extension (recommended in `.vscode/extensions.json`) for editing locale files. + ## Storybook Stories are co-located with pages as `*.stories.ts` files under `app/pages/`. Storybook configuration lives in `.storybook/`. @@ -426,7 +479,7 @@ Format: `type(scope): description` Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`, `types` -Scopes (optional): `auth`, `api`, `guild`, `ui`, `db`, `deps` +Scopes (optional): `auth`, `api`, `guild`, `ui`, `db`, `deps`, `i18n` Examples: diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 9f78b7814..34b33f42f 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -32,6 +32,12 @@ jobs: cache: true sfw: true + - name: 🌐 Compare translations + run: vp run i18n:check + + - name: 🌍 Update lunaria data + run: vp run build:lunaria + - name: 🔠 Fix lint errors run: vp run lint:fix diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ea0d7a26..9c2424549 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -284,3 +284,28 @@ jobs: - name: 🧹 Check for unused code run: vp run knip + + i18n: + name: 🌐 i18n validation + runs-on: blacksmith-4vcpu-ubuntu-2404-arm + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1.12.0 + with: + node-version: lts/* + sfw: true + # root only, no scripts + run-install: | + - args: ["--filter", ".", "--ignore-scripts"] + + - name: 🌐 Check for missing or dynamic i18n keys + run: vp run i18n:report + + - name: 🌐 Check i18n schema is up to date + run: | + vp run i18n:schema + git diff --exit-code i18n/schema.json diff --git a/.github/workflows/lunaria.yml b/.github/workflows/lunaria.yml new file mode 100644 index 000000000..6762d775a --- /dev/null +++ b/.github/workflows/lunaria.yml @@ -0,0 +1,37 @@ +name: lunaria + +on: + pull_request_target: + types: [opened, synchronize] + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request_target' && github.head_ref || github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + lunaria-overview: + name: 🌝 Generate Lunaria Overview + runs-on: blacksmith-4vcpu-ubuntu-2404-arm + permissions: + contents: read + pull-requests: write # post Lunaria overview comments on pull requests + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + # Necessary for Lunaria to work properly + # Makes the action clone the entire git history + fetch-depth: 0 + persist-credentials: false + + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1.12.0 + with: + node-version: lts/* + cache: true + sfw: true + + - name: Generate Lunaria Overview + uses: lunariajs/action@4911ad0736d1e3b20af4cb70f5079aea2327ed8e # astro-docs diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 57298b3d9..a101353d8 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -1,7 +1,12 @@ # Existing privileged PR automation is intentionally isolated to these workflows: # they do not checkout pull request head code, and they only comment or set status. rules: + stale-action-refs: + ignore: + # lunariajs/action has no tag refs; keep the branch commit hash-pinned. + - lunaria.yml:37 dangerous-triggers: ignore: - enforce-release-source.yml + - lunaria.yml - semantic-pull-requests.yml diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b41930292..0f40e4af0 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -11,6 +11,7 @@ "formulahendry.auto-rename-tag", "vitest.explorer", "coderabbit.coderabbit-vscode", - "VoidZero.vite-plus-extension-pack" + "VoidZero.vite-plus-extension-pack", + "lokalise.i18n-ally" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ce86954f..da55bc075 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,10 @@ // Disable the default formatter, use eslint instead "prettier.enable": false, "eslint.enable": false, + "i18n-ally.keystyle": "nested", + "i18n-ally.localesPaths": ["./i18n/locales"], + "i18n-ally.sourceLanguage": "en", + "i18n-ally.displayLanguage": "en", "editor.formatOnSave": true, "editor.formatOnSaveMode": "file", "oxc.fmt.configPath": "./vite.config.ts", diff --git a/AGENTS.md b/AGENTS.md index e775f503a..1f01bafa2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,6 +71,12 @@ pnpm knip:fix # Auto-fix unused files, exports, and dependenc pnpm preview # Preview production build locally pnpm lint:fix # Run linter and auto-fix issues (oxlint + oxfmt) pnpm typecheck # TypeScript type checking +pnpm vp run i18n:check # Audit locale files against en.json +pnpm i18n:check:fix # Sync locale keys (EN placeholders for missing) +pnpm vp run i18n:report # Fail on missing/unused/dynamic i18n keys in app/** +pnpm i18n:report:fix # Remove unused keys from all locale files +pnpm vp run i18n:schema # Regenerate i18n/schema.json from en.json +pnpm vp run build:lunaria # Build Lunaria dashboard + status.json pnpm test # Run all Vitest projects pnpm test:unit # Run unit tests pnpm test:nuxt # Nuxt component/API tests diff --git a/app/app.vue b/app/app.vue index 29322139d..a7c8d36b3 100644 --- a/app/app.vue +++ b/app/app.vue @@ -20,6 +20,18 @@ useAuthIdentity(); const router = useRouter(); const appName = ref<"wolfstar" | "staryl">("wolfstar"); +const { locale, locales } = useI18n(); + +const localeMap = Object.fromEntries( + locales.value.map((entry) => [entry.code, entry.dir ?? "ltr"]), +); + +useHead({ + htmlAttrs: { + lang: () => locale.value, + dir: () => localeMap[locale.value] ?? "ltr", + }, +}); // Watch for route changes to update appName watch( diff --git a/app/components/UserMenu.vue b/app/components/UserMenu.vue index 15a58a088..66884f28f 100644 --- a/app/components/UserMenu.vue +++ b/app/components/UserMenu.vue @@ -8,7 +8,7 @@ ? 'w-48 bg-base-200/90 border border-base-200 shadow-md rounded-md' : 'w-(--reka-dropdown-menu-trigger-width) bg-base-200/90 border border-base-200 shadow-md rounded-md', }" - aria-label="User account menu" + :aria-label="t('user_menu.account_menu')" > @@ -32,11 +36,14 @@ diff --git a/app/components/app/HeaderAuth.vue b/app/components/app/HeaderAuth.vue index 45ac4f30c..bfe8a9cff 100644 --- a/app/components/app/HeaderAuth.vue +++ b/app/components/app/HeaderAuth.vue @@ -18,7 +18,7 @@ class="flex cursor-pointer items-center gap-2" :class="mobile ? 'w-full' : undefined" role="button" - aria-label="User menu" + :aria-label="t('header.user_menu')" aria-haspopup="menu" tabindex="0" > @@ -46,10 +46,10 @@ block class="md:hidden" icon="ic:round-discord" - aria-label="Sign in with Discord" + :aria-label="t('header.sign_in_discord')" /> @@ -78,17 +78,18 @@ const { mobile = false } = defineProps<{ mobile?: boolean; }>(); +const { t } = useI18n(); const { signOut } = useUserSession(); -const items = ref([ +const items = computed(() => [ { icon: "lucide:user", - label: "Profile", + label: t("header.profile"), to: "/profile", }, { icon: "lucide:log-out", - label: "Sign out", + label: t("header.sign_out"), onSelect: logout, ui: { itemLeadingIcon: "text-error", diff --git a/app/components/app/LocaleSelect.vue b/app/components/app/LocaleSelect.vue new file mode 100644 index 000000000..4643b40f3 --- /dev/null +++ b/app/components/app/LocaleSelect.vue @@ -0,0 +1,35 @@ + + + diff --git a/app/composables/useFooter.ts b/app/composables/useFooter.ts index 2a85cd527..7b420fe34 100644 --- a/app/composables/useFooter.ts +++ b/app/composables/useFooter.ts @@ -1,61 +1,68 @@ import type { FooterColumn } from "@nuxt/ui"; export const useFooter = () => { + const { t } = useI18n(); + const columns = computed(() => [ { children: [ { class: "link-hover", - label: "Features", + label: t("footer.features"), to: "/#features", }, { class: "link-hover", - label: "Other Apps", + label: t("footer.other_apps"), to: "/#apps", }, { class: "link-hover", - label: "Changelog", + label: t("footer.changelog"), to: "https://github.com/wolfstar-project/wolfstar.rocks/releases", }, ], - label: "Product", + label: t("footer.product"), }, { children: [ { class: "link-hover", - label: "Support Server", + label: t("footer.support_server"), to: "https://join.wolfstar.rocks", }, { class: "link-hover", - label: "GitHub", + label: t("footer.github"), to: "https://repo.wolfstar.rocks", }, { class: "link-hover", - label: "Blog", + label: t("footer.blog"), to: "/blog", }, + { + class: "link-hover", + label: t("translation_status.title"), + to: "/translation-status", + }, ], - label: "Community", + label: t("footer.community"), }, { children: [ { class: "link-hover", - label: "Terms of Use", + label: t("footer.terms"), to: "/terms", }, { class: "link-hover", - label: "Privacy Policy", + label: t("footer.privacy"), to: "/privacy", }, ], - label: "Legal", + label: t("footer.legal"), }, ]); diff --git a/app/composables/useHeader.ts b/app/composables/useHeader.ts index a9d843736..4287db817 100644 --- a/app/composables/useHeader.ts +++ b/app/composables/useHeader.ts @@ -1,4 +1,6 @@ export function useHeader() { + const { t } = useI18n(); + // Safely inject appName with fallback to prevent SSR issues const appName = inject(ProviderAppNameKey, ref<"wolfstar" | "staryl">("wolfstar")); @@ -15,22 +17,22 @@ export function useHeader() { { children: [ { - description: "Tools to help you moderate your server", - label: "Moderation Tools", + description: t("nav.moderation_tools_description"), + label: t("nav.moderation_tools"), to: "#moderation-tools", }, { - description: "Track and log events in your server", - label: "Advanced Logging", + description: t("nav.advanced_logging_description"), + label: t("nav.advanced_logging"), to: "#advanced-logging", }, { - description: "Searchable moderation history for every action", - label: "Moderation Logs", + description: t("nav.moderation_logs_description"), + label: t("nav.moderation_logs"), to: "#moderation-logs", }, ], - label: "Features", + label: t("nav.features"), }, { children: [ @@ -45,14 +47,14 @@ export function useHeader() { to: "/staryl", }, ], - label: "Applications", + label: t("nav.applications"), }, { - label: "Commands", + label: t("nav.commands"), to: "/commands", }, { - label: "Blog", + label: t("nav.blog"), to: "/blog", }, ]); @@ -61,16 +63,16 @@ export function useHeader() { { children: [ { - label: "Moderation Tools", + label: t("nav.moderation_tools"), }, { - label: "Advanced Logging", + label: t("nav.advanced_logging"), }, { - label: "Moderation Logs", + label: t("nav.moderation_logs"), }, ], - label: "Features", + label: t("nav.features"), }, { children: [ @@ -85,19 +87,19 @@ export function useHeader() { to: "/staryl", }, ], - label: "Applications", + label: t("nav.applications"), }, { - label: "Commands", + label: t("nav.commands"), to: "/commands", }, { - label: "Blog", + label: t("nav.blog"), to: "/blog", }, { icon: "lucide:github", - label: "GitHub", + label: t("nav.github"), rel: "noopener noreferrer", target: "_blank", to: "https://github.com/wolfstar-project/wolfstar.rocks", diff --git a/app/composables/useI18nStatus.ts b/app/composables/useI18nStatus.ts new file mode 100644 index 000000000..8048c7855 --- /dev/null +++ b/app/composables/useI18nStatus.ts @@ -0,0 +1,62 @@ +/** + * Composable for accessing translation status data from Lunaria. + * Provides information about translation progress for each locale. + */ +export function useI18nStatus() { + const { locale: currentLocale } = useI18n(); + + const { + data: status, + status: fetchStatus, + error, + } = useFetch("/lunaria/status.json", { + responseType: "json", + server: false, + // Cache the result to avoid refetching on navigation + getCachedData: (key, nuxtApp) => nuxtApp.payload.data[key] ?? nuxtApp.static.data[key], + }); + + const localesMap = computed | undefined>(() => { + return status.value?.locales.reduce((acc, locale) => { + acc.set(locale.lang, locale); + return acc; + }, new Map()); + }); + + function getLocaleStatus(langCode: string): I18nLocaleStatus | null { + return localesMap.value?.get(langCode) ?? null; + } + + const currentLocaleStatus = computed(() => + getLocaleStatus(currentLocale.value), + ); + + const isComplete = computed(() => { + const localeStatus = currentLocaleStatus.value; + if (!localeStatus) return true; + return localeStatus.percentComplete === 100; + }); + + const isSourceLocale = computed(() => { + const sourceLang = status.value?.sourceLocale.lang ?? "en"; + return ( + currentLocale.value === sourceLang || currentLocale.value.startsWith(`${sourceLang}-`) + ); + }); + + const githubEditUrl = computed(() => { + return currentLocaleStatus.value?.githubEditUrl ?? null; + }); + + return { + status, + fetchStatus, + error, + getLocaleStatus, + currentLocaleStatus, + isComplete, + isSourceLocale, + githubEditUrl, + localesMap, + }; +} diff --git a/app/composables/usePreferredLocale.ts b/app/composables/usePreferredLocale.ts new file mode 100644 index 000000000..6be9ed63a --- /dev/null +++ b/app/composables/usePreferredLocale.ts @@ -0,0 +1,19 @@ +/** localStorage key for the user's preferred UI locale (not guild bot language). */ +const PREFERRED_LOCALE_KEY = "user-prefers-locale"; + +/** + * Persist and restore the user's preferred UI locale. + * Kept separate from guild bot-response language settings. + */ +export function usePreferredLocale() { + const preferredLocale = useLocalStorage(PREFERRED_LOCALE_KEY, null); + + function setPreferredLocale(code: string | null) { + preferredLocale.value = code; + } + + return { + preferredLocale, + setPreferredLocale, + }; +} diff --git a/app/layouts/default.vue b/app/layouts/default.vue index beff44f5f..3320844ef 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -4,11 +4,11 @@ href="#maincontent" class="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-[100] focus:rounded-lg focus:bg-primary focus:px-4 focus:py-2 focus:text-primary-content focus:shadow-lg focus:outline focus:outline-2 focus:outline-transparent" > - Skip to main content + {{ t("a11y.skip_to_content") }} - + @@ -34,6 +34,7 @@ diff --git a/app/pages/translation-status.vue b/app/pages/translation-status.vue new file mode 100644 index 000000000..29b2c76d1 --- /dev/null +++ b/app/pages/translation-status.vue @@ -0,0 +1,107 @@ + + + diff --git a/app/plugins/i18n-loader.client.ts b/app/plugins/i18n-loader.client.ts new file mode 100644 index 000000000..7f5f340af --- /dev/null +++ b/app/plugins/i18n-loader.client.ts @@ -0,0 +1,16 @@ +import { isAppLocaleCode } from "~/utils/is-app-locale"; + +export default defineNuxtPlugin({ + enforce: "post", + env: { islands: false }, + setup() { + const { $i18n } = useNuxtApp(); + const { locale, setLocale } = $i18n; + const { preferredLocale } = usePreferredLocale(); + const storedLocale = preferredLocale.value; + + if (storedLocale && isAppLocaleCode(storedLocale) && storedLocale !== locale.value) { + void setLocale(storedLocale); + } + }, +}); diff --git a/app/utils/is-app-locale.ts b/app/utils/is-app-locale.ts new file mode 100644 index 000000000..c721d883d --- /dev/null +++ b/app/utils/is-app-locale.ts @@ -0,0 +1,9 @@ +import { currentLocales } from "~~/config/i18n"; + +export type AppLocaleCode = (typeof currentLocales)[number]["code"]; + +const APP_LOCALE_CODES = new Set(currentLocales.map((locale) => locale.code)); + +export function isAppLocaleCode(value: string): value is AppLocaleCode { + return APP_LOCALE_CODES.has(value); +} diff --git a/app/utils/marketing-routes.ts b/app/utils/marketing-routes.ts index b5cfe9fbb..b3ba71900 100644 --- a/app/utils/marketing-routes.ts +++ b/app/utils/marketing-routes.ts @@ -8,6 +8,7 @@ export const MARKETING_PATHS = [ "/profile", "/account", "/blog", + "/translation-status", ] as const; export function isMarketingPath(path: string): boolean { diff --git a/config/i18n.ts b/config/i18n.ts new file mode 100644 index 000000000..7bea73ac0 --- /dev/null +++ b/config/i18n.ts @@ -0,0 +1,132 @@ +import type { + DateTimeFormats, + NumberFormats, + PluralizationRule, + PluralizationRules, +} from "@intlify/core-base"; +import type { LocaleObject } from "@nuxtjs/i18n"; + +interface LocaleObjectData extends LocaleObject { + numberFormats?: NumberFormats; + dateTimeFormats?: DateTimeFormats; + pluralRule?: PluralizationRule; +} + +/** + * Country / regional variants that inherit from a base language file. + * e.g. `en` → `en-US` / `en-GB`, mirroring npmx.dev's merge model. + * Keep empty until a base language gains regional overrides. + */ +export const countryLocaleVariants: Record = { + // Example for future expansion: + // en: [ + // { country: true, code: "en-US", name: "English (US)" }, + // { code: "en-GB", name: "English (UK)" }, + // ], +}; + +const locales: LocaleObjectData[] = [ + { + code: "en", + file: "en.json", + name: "English", + language: "en-US", + }, + { + // Matches WolfStar/Skyra bot language keys (e.g. guild settings `language`) + code: "es-ES", + file: "es-ES.json", + name: "Español", + language: "es-ES", + }, +]; + +function buildLocales() { + const useLocales = locales.reduce((acc, data) => { + const localeVariants = countryLocaleVariants[data.code]; + if (localeVariants) { + for (const variant of localeVariants) { + const entry: LocaleObjectData = { + ...data, + code: variant.code, + name: variant.name, + files: [data.file as string, `${variant.code}.json`], + }; + delete entry.file; + acc.push(entry); + } + } else { + acc.push(data); + } + return acc; + }, [] as LocaleObjectData[]); + + return useLocales.toSorted((a, b) => a.code.localeCompare(b.code)); +} + +export const currentLocales = buildLocales(); + +export const datetimeFormats = Object.values(currentLocales).reduce((acc, data) => { + const dateTimeFormats = data.dateTimeFormats; + if (dateTimeFormats) { + acc[data.code] = { ...dateTimeFormats }; + delete data.dateTimeFormats; + } else { + acc[data.code] = { + shortDate: { + dateStyle: "short", + }, + short: { + dateStyle: "short", + timeStyle: "short", + }, + long: { + dateStyle: "long", + timeStyle: "medium", + }, + }; + } + + return acc; +}, {} as DateTimeFormats); + +export const numberFormats = Object.values(currentLocales).reduce((acc, data) => { + const numberFormatsArray = data.numberFormats; + if (numberFormatsArray) { + acc[data.code] = { ...numberFormatsArray }; + delete data.numberFormats; + } else { + acc[data.code] = { + percentage: { + style: "percent", + maximumFractionDigits: 1, + }, + smallCounting: { + style: "decimal", + maximumFractionDigits: 0, + }, + kiloCounting: { + notation: "compact", + compactDisplay: "short", + maximumFractionDigits: 1, + }, + millionCounting: { + notation: "compact", + compactDisplay: "short", + maximumFractionDigits: 2, + }, + }; + } + + return acc; +}, {} as NumberFormats); + +export const pluralRules = Object.values(currentLocales).reduce((acc, data) => { + const pluralRule = data.pluralRule; + if (pluralRule) { + acc[data.code] = pluralRule; + delete data.pluralRule; + } + + return acc; +}, {} as PluralizationRules); diff --git a/i18n/i18n.config.ts b/i18n/i18n.config.ts new file mode 100644 index 000000000..c375fdb46 --- /dev/null +++ b/i18n/i18n.config.ts @@ -0,0 +1,13 @@ +import { currentLocales, datetimeFormats, numberFormats, pluralRules } from "../config/i18n"; + +export default defineI18nConfig(() => { + return { + availableLocales: currentLocales.map((l) => l.code), + fallbackLocale: "en", + fallbackWarn: true, + missingWarn: true, + datetimeFormats, + numberFormats, + pluralRules, + }; +}); diff --git a/i18n/locales/en.json b/i18n/locales/en.json new file mode 100644 index 000000000..bd9930d3c --- /dev/null +++ b/i18n/locales/en.json @@ -0,0 +1,80 @@ +{ + "$schema": "../schema.json", + "nav": { + "features": "Features", + "moderation_tools": "Moderation Tools", + "moderation_tools_description": "Tools to help you moderate your server", + "advanced_logging": "Advanced Logging", + "advanced_logging_description": "Track and log events in your server", + "moderation_logs": "Moderation Logs", + "moderation_logs_description": "Searchable moderation history for every action", + "applications": "Applications", + "commands": "Commands", + "blog": "Blog", + "github": "GitHub", + "main_navigation": "Main navigation", + "mobile_navigation": "Mobile navigation" + }, + "header": { + "site_header": "Site header", + "home": "{name} home", + "add_app": "Add App", + "sign_in": "Sign in", + "sign_in_discord": "Sign in with Discord", + "user_menu": "User menu", + "profile": "Profile", + "sign_out": "Sign out" + }, + "footer": { + "site_footer": "Site footer", + "tagline": "A fully customizable, multilingual Discord moderation app. Free forever, open source.", + "logo": "WolfStar logo", + "powered_by_netlify": "Powered by Netlify", + "powered_by_netlify_aria": "Powered by Netlify - opens in new tab", + "copyright": "WolfStar Project — Copyright © {year}. All rights reserved.", + "github_aria": "Visit WolfStar on GitHub - opens in new tab", + "column_links": "Footer {label} links", + "product": "Product", + "features": "Features", + "other_apps": "Other Apps", + "changelog": "Changelog", + "community": "Community", + "support_server": "Support Server", + "github": "GitHub", + "blog": "Blog", + "legal": "Legal", + "terms": "Terms of Use", + "privacy": "Privacy Policy" + }, + "a11y": { + "skip_to_content": "Skip to main content", + "main_content": "Main content" + }, + "common": { + "language": "Language", + "appearance": "Appearance", + "light": "Light", + "dark": "Dark" + }, + "user_menu": { + "account_menu": "User account menu", + "user_menu_for": "User menu for {name}", + "profile": "Profile", + "report_bug": "Report a Bug", + "sign_out": "Sign out", + "avatar_alt": "{name}'s avatar", + "avatar_fallback": "User avatar" + }, + "i18n": { + "missing_keys": "No missing keys | 1 missing key | {count} missing keys", + "edit_on_github": "Edit on GitHub" + }, + "translation_status": { + "title": "Translation Status", + "description": "Track how complete each locale is and contribute missing strings.", + "generated_at": "Last generated {date}", + "source_locale": "Source locale", + "progress_by_locale": "Progress by locale", + "no_data": "Translation status is not available yet. Build the Lunaria dashboard to generate it." + } +} diff --git a/i18n/locales/es-ES.json b/i18n/locales/es-ES.json new file mode 100644 index 000000000..0525d08ca --- /dev/null +++ b/i18n/locales/es-ES.json @@ -0,0 +1,80 @@ +{ + "$schema": "../schema.json", + "nav": { + "features": "Funciones", + "moderation_tools": "Herramientas de moderación", + "moderation_tools_description": "Herramientas para ayudarte a moderar tu servidor", + "advanced_logging": "Registro avanzado", + "advanced_logging_description": "Rastrea y registra eventos en tu servidor", + "moderation_logs": "Registros de moderación", + "moderation_logs_description": "Historial de moderación con búsqueda para cada acción", + "applications": "Aplicaciones", + "commands": "Comandos", + "blog": "Blog", + "github": "GitHub", + "main_navigation": "Navegación principal", + "mobile_navigation": "Navegación móvil" + }, + "header": { + "site_header": "Cabecera del sitio", + "home": "Inicio de {name}", + "add_app": "Añadir app", + "sign_in": "Iniciar sesión", + "sign_in_discord": "Iniciar sesión con Discord", + "user_menu": "Menú de usuario", + "profile": "Perfil", + "sign_out": "Cerrar sesión" + }, + "footer": { + "site_footer": "Pie de página del sitio", + "tagline": "Una app de moderación de Discord totalmente personalizable y multilingüe. Gratis para siempre, de código abierto.", + "logo": "Logo de WolfStar", + "powered_by_netlify": "Powered by Netlify", + "powered_by_netlify_aria": "Powered by Netlify - se abre en una pestaña nueva", + "copyright": "WolfStar Project — Copyright © {year}. Todos los derechos reservados.", + "github_aria": "Visita WolfStar en GitHub - se abre en una pestaña nueva", + "column_links": "Enlaces del pie de página de {label}", + "product": "Producto", + "features": "Funciones", + "other_apps": "Otras apps", + "changelog": "Registro de cambios", + "community": "Comunidad", + "support_server": "Servidor de soporte", + "github": "GitHub", + "blog": "Blog", + "legal": "Legal", + "terms": "Términos de uso", + "privacy": "Política de privacidad" + }, + "a11y": { + "skip_to_content": "Saltar al contenido principal", + "main_content": "Contenido principal" + }, + "common": { + "language": "Idioma", + "appearance": "Apariencia", + "light": "Claro", + "dark": "Oscuro" + }, + "user_menu": { + "account_menu": "Menú de cuenta de usuario", + "user_menu_for": "Menú de usuario de {name}", + "profile": "Perfil", + "report_bug": "Informar de un error", + "sign_out": "Cerrar sesión", + "avatar_alt": "Avatar de {name}", + "avatar_fallback": "Avatar de usuario" + }, + "i18n": { + "missing_keys": "Ninguna clave faltante | 1 clave faltante | {count} claves faltantes", + "edit_on_github": "Editar en GitHub" + }, + "translation_status": { + "title": "Estado de las traducciones", + "description": "Consulta qué tan completa está cada lengua y contribuye con las cadenas que falten.", + "generated_at": "Última generación {date}", + "source_locale": "Idioma de origen", + "progress_by_locale": "Progreso por idioma", + "no_data": "El estado de las traducciones aún no está disponible. Genera el panel de Lunaria para crearlo." + } +} diff --git a/i18n/schema.json b/i18n/schema.json new file mode 100644 index 000000000..12cf61f8b --- /dev/null +++ b/i18n/schema.json @@ -0,0 +1,243 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "WolfStar i18n locale file", + "description": "Schema for WolfStar i18n translation files. Generated from en.json — do not edit manually.", + "type": "object", + "properties": { + "nav": { + "type": "object", + "properties": { + "features": { + "type": "string" + }, + "moderation_tools": { + "type": "string" + }, + "moderation_tools_description": { + "type": "string" + }, + "advanced_logging": { + "type": "string" + }, + "advanced_logging_description": { + "type": "string" + }, + "moderation_logs": { + "type": "string" + }, + "moderation_logs_description": { + "type": "string" + }, + "applications": { + "type": "string" + }, + "commands": { + "type": "string" + }, + "blog": { + "type": "string" + }, + "github": { + "type": "string" + }, + "main_navigation": { + "type": "string" + }, + "mobile_navigation": { + "type": "string" + } + }, + "additionalProperties": false + }, + "header": { + "type": "object", + "properties": { + "site_header": { + "type": "string" + }, + "home": { + "type": "string" + }, + "add_app": { + "type": "string" + }, + "sign_in": { + "type": "string" + }, + "sign_in_discord": { + "type": "string" + }, + "user_menu": { + "type": "string" + }, + "profile": { + "type": "string" + }, + "sign_out": { + "type": "string" + } + }, + "additionalProperties": false + }, + "footer": { + "type": "object", + "properties": { + "site_footer": { + "type": "string" + }, + "tagline": { + "type": "string" + }, + "logo": { + "type": "string" + }, + "powered_by_netlify": { + "type": "string" + }, + "powered_by_netlify_aria": { + "type": "string" + }, + "copyright": { + "type": "string" + }, + "github_aria": { + "type": "string" + }, + "column_links": { + "type": "string" + }, + "product": { + "type": "string" + }, + "features": { + "type": "string" + }, + "other_apps": { + "type": "string" + }, + "changelog": { + "type": "string" + }, + "community": { + "type": "string" + }, + "support_server": { + "type": "string" + }, + "github": { + "type": "string" + }, + "blog": { + "type": "string" + }, + "legal": { + "type": "string" + }, + "terms": { + "type": "string" + }, + "privacy": { + "type": "string" + } + }, + "additionalProperties": false + }, + "a11y": { + "type": "object", + "properties": { + "skip_to_content": { + "type": "string" + }, + "main_content": { + "type": "string" + } + }, + "additionalProperties": false + }, + "common": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "appearance": { + "type": "string" + }, + "light": { + "type": "string" + }, + "dark": { + "type": "string" + } + }, + "additionalProperties": false + }, + "user_menu": { + "type": "object", + "properties": { + "account_menu": { + "type": "string" + }, + "user_menu_for": { + "type": "string" + }, + "profile": { + "type": "string" + }, + "report_bug": { + "type": "string" + }, + "sign_out": { + "type": "string" + }, + "avatar_alt": { + "type": "string" + }, + "avatar_fallback": { + "type": "string" + } + }, + "additionalProperties": false + }, + "i18n": { + "type": "object", + "properties": { + "missing_keys": { + "type": "string" + }, + "edit_on_github": { + "type": "string" + } + }, + "additionalProperties": false + }, + "translation_status": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "generated_at": { + "type": "string" + }, + "source_locale": { + "type": "string" + }, + "progress_by_locale": { + "type": "string" + }, + "no_data": { + "type": "string" + } + }, + "additionalProperties": false + }, + "$schema": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/knip.ts b/knip.ts index 5d24316c9..592eccaa3 100644 --- a/knip.ts +++ b/knip.ts @@ -12,6 +12,11 @@ const config: KnipConfig = { ".lighthouserc.cjs", "lighthouse-setup.cjs", "scripts/**/*.ts", + /** i18n / Lunaria entrypoints (not statically imported by the app) */ + "i18n/**/*.ts", + "lunaria.config.ts", + "lunaria.config.json", + "lunaria/lunaria.ts", /** Loaded by @nuxt/content at build time, not imported directly */ "content.config.ts", /** MDC components rendered from Markdown (e.g. ::card, ::note), so usage isn't statically visible */ diff --git a/lunaria.config.json b/lunaria.config.json new file mode 100644 index 000000000..64d901970 --- /dev/null +++ b/lunaria.config.json @@ -0,0 +1,48 @@ +{ + "$schema": "./node_modules/@lunariajs/core/config.schema.json", + "repository": { + "name": "wolfstar-project/wolfstar.rocks" + }, + "defaultLocale": { + "label": "English", + "lang": "en" + }, + "locales": [ + { + "label": "Español", + "lang": "es-ES" + } + ], + "files": [ + { + "location": "i18n/locales/en.json", + "pattern": "i18n/locales/@lang.json", + "type": "dictionary", + "optionalKeys": { + "i18n/locales/.json": ["$schema"] + } + } + ], + "ignoreKeywords": [ + "lunaria-ignore", + "typo", + "en-only", + "broken link", + "i18nReady", + "i18nIgnore", + "fix typo" + ], + "dashboard": { + "title": "WolfStar Translation Status", + "description": "Translation progress tracker for wolfstar.rocks. See how much has been translated and get involved!", + "site": "https://wolfstar.rocks/lunaria/", + "favicon": { + "external": [ + { + "link": "https://wolfstar.rocks/favicon.ico", + "type": "image/x-icon" + } + ] + } + } +} diff --git a/lunaria.config.ts b/lunaria.config.ts new file mode 100644 index 000000000..33aa8c535 --- /dev/null +++ b/lunaria.config.ts @@ -0,0 +1,13 @@ +import type { LunariaUserConfig } from "@lunariajs/core/config"; +import configJson from "./lunaria.config.json" with { type: "json" }; + +/** + * Typed Lunaria config shared with `lunaria/lunaria.ts`. + * The JSON file is the source of truth for the Lunaria CLI / GitHub Action + * (stable `@lunariajs/core` only loads `.json` configs). + * + * Keep `locales` in sync with `config/i18n.ts` when adding languages. + */ +const config = configJson as LunariaUserConfig; + +export default config; diff --git a/lunaria/lunaria.ts b/lunaria/lunaria.ts new file mode 100644 index 000000000..13667f65a --- /dev/null +++ b/lunaria/lunaria.ts @@ -0,0 +1,175 @@ +import type { I18nStatus } from "../shared/types/i18n-status.ts"; +import { execFileSync } from "node:child_process"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import process from "node:process"; +import { currentLocales } from "../config/i18n.ts"; +import config from "../lunaria.config.ts"; + +// Skip during git merges — history may be inconsistent. +if (existsSync(".git/MERGE_HEAD")) { + // eslint-disable-next-line no-console + console.log("Skipping lunaria: git merge in progress"); + process.exit(0); +} + +type NestedRecord = Record; + +function countKeys(obj: NestedRecord): number { + let count = 0; + for (const key in obj) { + const value = obj[key]; + if (typeof value === "object" && value !== null && !Array.isArray(value)) { + count += countKeys(value as NestedRecord); + } else { + count++; + } + } + return count; +} + +function collectMissingKeys(source: NestedRecord, target: NestedRecord, prefix = ""): string[] { + const missing: string[] = []; + for (const key of Object.keys(source)) { + const path = prefix ? `${prefix}.${key}` : key; + const sourceValue = source[key]; + const targetValue = target[key]; + + if ( + typeof sourceValue === "object" && + sourceValue !== null && + !Array.isArray(sourceValue) + ) { + const nextTarget = + typeof targetValue === "object" && + targetValue !== null && + !Array.isArray(targetValue) + ? (targetValue as NestedRecord) + : {}; + missing.push(...collectMissingKeys(sourceValue as NestedRecord, nextTarget, path)); + } else if (!(key in target)) { + missing.push(path); + } + } + return missing; +} + +function buildJsonStatus(): I18nStatus { + const sourceContent = JSON.parse(readFileSync("i18n/locales/en.json", "utf-8")) as NestedRecord; + const { $schema: _, ...sourceWithoutMeta } = sourceContent; + const totalKeys = countKeys(sourceWithoutMeta); + + const { defaultLocale, repository } = config; + const repoName = repository.name; + const branch = + "branch" in repository && typeof repository.branch === "string" + ? repository.branch + : "main"; + const githubBase = `https://github.com/${repoName}`; + + const appLocales = currentLocales.filter((l) => l.code !== defaultLocale.lang && l.name); + + return { + generatedAt: new Date().toISOString(), + sourceLocale: { + lang: defaultLocale.lang, + label: defaultLocale.label, + totalKeys, + }, + locales: appLocales.map((locale) => { + const localeFilePath = `i18n/locales/${locale.code}.json`; + let localeContent: NestedRecord = {}; + if (existsSync(localeFilePath)) { + localeContent = JSON.parse(readFileSync(localeFilePath, "utf-8")) as NestedRecord; + } + const { $schema: __, ...localeWithoutMeta } = localeContent; + const missingKeys = collectMissingKeys(sourceWithoutMeta, localeWithoutMeta); + const completedKeys = totalKeys - missingKeys.length; + + return { + lang: locale.code, + label: locale.name!, + dir: locale.dir ?? "ltr", + totalKeys, + completedKeys, + missingKeys, + percentComplete: + totalKeys > 0 ? Math.round((completedKeys / totalKeys) * 100) : 100, + githubEditUrl: `${githubBase}/blob/${branch}/${localeFilePath}`, + githubHistoryUrl: `${githubBase}/commits/${branch}/${localeFilePath}`, + }; + }), + }; +} + +function fallbackDashboardHtml(status: I18nStatus): string { + const rows = status.locales + .map( + (locale) => ` +
+ + ${locale.label} ${locale.lang} +
+
+ ${locale.missingKeys.length ? `${locale.missingKeys.length} missing keys` : "✔"} + ${locale.completedKeys} / ${locale.totalKeys} +
+
+

Edit on GitHub

+ ${ + locale.missingKeys.length + ? `
    ${locale.missingKeys.map((key) => `
  • ${key}
  • `).join("")}
` + : "

This translation is complete.

" + } +
`, + ) + .join("\n"); + + return ` + + + + + WolfStar Translation Status + + + +
+

WolfStar Translation Status

+

Fallback dashboard (Lunaria git status unavailable — usually due to uncommitted locale changes).

+

Source: ${status.sourceLocale.label} (${status.sourceLocale.lang}) — ${status.sourceLocale.totalKeys} keys

+ ${rows} +
+ + +`; +} + +const jsonStatus = buildJsonStatus(); +const outDir = "dist/lunaria"; +mkdirSync(outDir, { recursive: true }); +writeFileSync(join(outDir, "status.json"), `${JSON.stringify(jsonStatus, null, 2)}\n`); + +// Lunaria calls process.exit(1) on dirty working trees, so run the official +// dashboard build in a child process and keep a fallback HTML if it fails. +try { + execFileSync(join("node_modules", ".bin", "lunaria"), ["build"], { + stdio: "inherit", + cwd: process.cwd(), + }); + // Preserve our nested-key status.json (CLI build may not write it). + writeFileSync(join(outDir, "status.json"), `${JSON.stringify(jsonStatus, null, 2)}\n`); + // eslint-disable-next-line no-console + console.log("Generated dist/lunaria/index.html and dist/lunaria/status.json"); +} catch { + writeFileSync(join(outDir, "index.html"), fallbackDashboardHtml(jsonStatus)); + // eslint-disable-next-line no-console + console.warn( + "Lunaria git dashboard failed; wrote fallback HTML + status.json (commit locale files for the full dashboard).", + ); +} diff --git a/modules/lunaria.ts b/modules/lunaria.ts new file mode 100644 index 000000000..5a20d6a08 --- /dev/null +++ b/modules/lunaria.ts @@ -0,0 +1,54 @@ +import { execSync } from "node:child_process"; +import { existsSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { defineNuxtModule, useNuxt } from "nuxt/kit"; +import { isCI, isTest } from "std-env"; +import { getEnv } from "../config/env.ts"; + +/** + * Serves the Lunaria translation dashboard and status.json from `dist/lunaria/`. + * Based on: https://github.com/npmx-dev/npmx.dev/blob/main/modules/lunaria.ts + */ +export default defineNuxtModule({ + meta: { + name: "wolfstar:lunaria", + }, + setup() { + const nuxt = useNuxt(); + + const lunariaDistPath = join(nuxt.options.rootDir, "dist/lunaria/"); + + nuxt.options.nitro.publicAssets ||= []; + nuxt.options.nitro.publicAssets.push({ + dir: lunariaDistPath, + baseURL: "/lunaria/", + maxAge: 60 * 60 * 24, // 1 day + }); + + if (nuxt.options.dev || nuxt.options._prepare || nuxt.options.test || isTest) { + return; + } + + if (!isCI || !existsSync(lunariaDistPath)) { + mkdirSync(lunariaDistPath, { recursive: true }); + nuxt.hook("nitro:build:before", async () => { + try { + execSync("node --experimental-transform-types ./lunaria/lunaria.ts", { + cwd: nuxt.options.rootDir, + stdio: "inherit", + }); + } catch (error) { + // Always throw in local builds. + // In CI, only throw when building for production/canary release. + const { env } = await getEnv(!isCI); + if (env === "dev" || env === "release" || env === "canary") { + throw error; + } + // Preview builds: keep shipping even if Lunaria git status fails. + // oxlint-disable-next-line no-console -- build-time diagnostic only + console.error(error); + } + }); + } + }, +}); diff --git a/nuxt.config.ts b/nuxt.config.ts index c12998da5..9320c0f1a 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -2,6 +2,7 @@ import netlifyNuxt from "@netlify/nuxt"; import { auditRedactPreset } from "evlog"; import { createResolver } from "nuxt/kit"; import { isCI, isTest, provider } from "std-env"; +import { currentLocales } from "./config/i18n"; import { pwa } from "./config/pwa"; import { generateRuntimeConfig } from "./server/utils/runtimeConfig"; @@ -25,6 +26,7 @@ export default defineNuxtConfig({ "@vueuse/nuxt", "@vite-pwa/nuxt", "@nuxtjs/html-validator", + "@nuxtjs/i18n", "@vueuse/motion/nuxt", "@sentry/nuxt/module", "evlog/nuxt", @@ -86,7 +88,7 @@ export default defineNuxtConfig({ app: { head: { charset: "utf-8", - htmlAttrs: { lang: "en" }, + htmlAttrs: { lang: "en-US" }, link: [ // Preconnect for external domains (faster than dns-prefetch: includes TLS handshake) { href: "https://cdn.discordapp.com", rel: "preconnect", crossorigin: "anonymous" }, @@ -263,6 +265,13 @@ export default defineNuxtConfig({ "/wolfstar": { appLayout: "default", prerender: true, robots: true }, "/blog": { appLayout: "default", prerender: true, robots: true }, "/blog/**": { appLayout: "default", prerender: true, robots: true }, + "/translation-status": { appLayout: "default", prerender: true, robots: true }, + // lunaria status.json — always revalidate so the app sees fresh progress + "/lunaria/status.json": { + headers: { + "Cache-Control": "public, max-age=0, must-revalidate", + }, + }, }, sourcemap: { @@ -622,6 +631,14 @@ export default defineNuxtConfig({ disablePreloadLinks: true, disableStylesheets: "entry", }, + + i18n: { + locales: currentLocales, + defaultLocale: "en", + strategy: "no_prefix", + detectBrowserLanguage: false, + langDir: "locales", + }, }); interface ISRConfigOptions { diff --git a/package.json b/package.json index da87d7449..80375f507 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "audit:verify": "tsx scripts/audit-verify.ts", "design:lint": "designmd lint .claude/DESIGN.md", "build": "nuxt build", + "i18n:check:fix": "node --experimental-transform-types scripts/compare-translations.ts --fix", + "i18n:report:fix": "node --experimental-transform-types scripts/remove-unused-translations.ts", "build:test": "NODE_ENV=test vp run build", "dev": "nuxt dev", "dev:pwa": "VITE_DEV_PWA=true nuxt dev", @@ -67,6 +69,9 @@ "@iconify-json/ic": "^1.2.4", "@iconify-json/lucide": "^1.2.96", "@iconify-json/ph": "^1.2.2", + "@intlify/core-base": "11.4.6", + "@intlify/shared": "11.4.6", + "@lunariajs/core": "0.1.1", "@nuxt/a11y": "^1.0.0-alpha.1", "@nuxt/content": "^3.14.0", "@nuxt/fonts": "^0.14.0", @@ -74,6 +79,7 @@ "@nuxt/image": "^2.0.0", "@nuxt/ui": "^4.5.1", "@nuxtjs/html-validator": "2.1.0", + "@nuxtjs/i18n": "10.4.1", "@nuxtjs/seo": "^5.3.2", "@onmax/nuxt-better-auth": "^0.0.4", "@playwright/test": "^1.61.1", @@ -157,6 +163,7 @@ "tsx": "4.22.3", "typescript": "6.0.3", "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.19", + "vue-i18n-extract": "2.0.7", "vue-tsc": "3.3.2" }, "config": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ca2be832..7a824a115 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,6 +60,15 @@ importers: '@iconify-json/ph': specifier: ^1.2.2 version: 1.2.2 + '@intlify/core-base': + specifier: 11.4.6 + version: 11.4.6 + '@intlify/shared': + specifier: 11.4.6 + version: 11.4.6 + '@lunariajs/core': + specifier: 0.1.1 + version: 0.1.1 '@nuxt/a11y': specifier: ^1.0.0-alpha.1 version: 1.0.0-alpha.1(@voidzero-dev/vite-plus-core@0.1.19)(magicast@0.5.3) @@ -81,6 +90,9 @@ importers: '@nuxtjs/html-validator': specifier: 2.1.0 version: 2.1.0(@voidzero-dev/vite-plus-test@0.1.19)(magicast@0.5.3) + '@nuxtjs/i18n': + specifier: 10.4.1 + version: 10.4.1(@netlify/blobs@10.7.9)(@voidzero-dev/vite-plus-core@0.1.19)(@vue/compiler-dom@3.5.39)(db0@0.3.4)(esbuild@0.28.1)(eslint@9.39.4)(ioredis@5.11.1)(magicast@0.5.3)(rolldown@1.1.4)(rollup@4.62.2)(typescript@6.0.3)(vue@3.5.38) '@nuxtjs/seo': specifier: ^5.3.2 version: 5.3.2(98e31a5b8b87d58861b50247c65073a6) @@ -325,6 +337,9 @@ importers: vitest: specifier: npm:@voidzero-dev/vite-plus-test@0.1.19 version: '@voidzero-dev/vite-plus-test@0.1.19(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.7)(@vitest/ui@4.1.7)(@voidzero-dev/vite-plus-core@0.1.19)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@29.1.1)(terser@5.48.0)(tsx@4.22.3)(typescript@6.0.3)(yaml@2.9.0)' + vue-i18n-extract: + specifier: 2.0.7 + version: 2.0.7 vue-tsc: specifier: 3.3.2 version: 3.3.2(typescript@6.0.3) @@ -1229,6 +1244,9 @@ packages: '@chevrotain/utils@10.5.0': resolution: {integrity: sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==} + '@clack/core@0.3.5': + resolution: {integrity: sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==} + '@clack/core@0.5.0': resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} @@ -1493,6 +1511,12 @@ packages: resolution: {integrity: sha512-cxgkB66RQB95H3X27jlnxCRNTmPuSTgmBAq6/4n2Dtv4hsk4yz8FadA1ggmd0uZzvKqWD6CR+WFgTjhDqg7eyw==} engines: {node: '>=18.0.0'} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} @@ -1511,6 +1535,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.27.7': resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} @@ -1529,6 +1559,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.27.7': resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} @@ -1547,6 +1583,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.27.7': resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} @@ -1565,6 +1607,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.27.7': resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} @@ -1583,6 +1631,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.27.7': resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} @@ -1601,6 +1655,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} @@ -1619,6 +1679,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} @@ -1637,6 +1703,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.27.7': resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} @@ -1655,6 +1727,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.27.7': resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} @@ -1673,6 +1751,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.27.7': resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} @@ -1691,6 +1775,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.27.7': resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} @@ -1709,6 +1799,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.27.7': resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} @@ -1727,6 +1823,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.27.7': resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} @@ -1745,6 +1847,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.27.7': resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} @@ -1763,6 +1871,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.27.7': resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} @@ -1781,6 +1895,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.27.7': resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} @@ -1799,6 +1919,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} @@ -1817,6 +1943,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} @@ -1835,6 +1967,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} @@ -1853,6 +1991,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} @@ -1871,6 +2015,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} @@ -1889,6 +2039,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.27.7': resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} @@ -1907,6 +2063,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.27.7': resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} @@ -1925,6 +2087,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.27.7': resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} @@ -1943,6 +2111,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.27.7': resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} @@ -2338,6 +2512,84 @@ packages: '@internationalized/number@3.6.7': resolution: {integrity: sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==} + '@intlify/bundle-utils@11.2.4': + resolution: {integrity: sha512-eE18yR9eM9k5n8snCkHIYp2MuVTxa19aF8z9OMyxXWv0frz2HlBZDGIPFjA38pP3OJ1IlRBXC/dW5GILeLMSCQ==} + engines: {node: '>= 22.13'} + peerDependencies: + petite-vue-i18n: '*' + vue-i18n: '*' + peerDependenciesMeta: + petite-vue-i18n: + optional: true + vue-i18n: + optional: true + + '@intlify/core-base@11.4.6': + resolution: {integrity: sha512-EOeHO95XESK9IFHgHeZXunsM/WBAoCA0DlaWODvx14vKmetAuS97t+l6Xe9hTUqntPpF93vtVSjjUDafw3wXMw==} + engines: {node: '>= 22'} + + '@intlify/core@11.4.6': + resolution: {integrity: sha512-TqqW/Ew3w09zHjQh2A+toe766zwo/VjyAJZtmSd7lvsLurHAHofXKIkT3DCuKZ7aKlZyq4KUOidfntPnF5iNkA==} + engines: {node: '>= 22'} + + '@intlify/devtools-types@11.4.6': + resolution: {integrity: sha512-wowQPpNem56b2d43IJmqbrzG2FeBKe5f/kUGlpNuBmXs6OSqncF8m1+1lxHuW8ISZJF0ma2RkW3iLkw0g0G4VA==} + engines: {node: '>= 22'} + + '@intlify/h3@0.7.4': + resolution: {integrity: sha512-BtL5+U3Dd9Qz6so+ArOMQWZ+nV21rOqqYUXnqwvW6J3VUXr66A9+9+vUFb/NAQvOU4kdfkO3c/9LMRGU9WZ8vw==} + engines: {node: '>= 20'} + + '@intlify/message-compiler@11.4.6': + resolution: {integrity: sha512-5nj3jULqeTAC1WovwMs1LQWgatTa2pM/rXN9T3XW8rdOtXW9ZF6/GLSNFTKDQmPLwclhPdgUWLJ/4w3fMeeC/Q==} + engines: {node: '>= 22'} + + '@intlify/shared@11.4.6': + resolution: {integrity: sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA==} + engines: {node: '>= 22'} + + '@intlify/unplugin-vue-i18n@11.2.4': + resolution: {integrity: sha512-bY0ZOaVUvWTyvy4bRGCUKw4Brx5uH/ojjVKsZ1aWzY2drFKIJbeP8DpGMD2QZT8aLpZSUsHtiJlRGLQSZenrvw==} + engines: {node: '>= 22.13'} + peerDependencies: + petite-vue-i18n: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: 3.5.38 + vue-i18n: '*' + peerDependenciesMeta: + petite-vue-i18n: + optional: true + vite: + optional: true + vue-i18n: + optional: true + + '@intlify/utils@0.13.0': + resolution: {integrity: sha512-8i3uRdAxCGzuHwfmHcVjeLQBtysQB2aXl/ojoagDut5/gY5lvWCQ2+cnl2TiqE/fXj/D8EhWG/SLKA7qz4a3QA==} + engines: {node: '>= 18'} + + '@intlify/utils@0.14.1': + resolution: {integrity: sha512-/NVDhX6sG87h0PXIwUCTW9DeHbKeqlni6qVV8xzMULQRHE9azIETldBlTKaBji7z6ostyDIH4s6SWI3AAI4uFg==} + engines: {node: '>= 20'} + + '@intlify/vue-i18n-extensions@8.0.0': + resolution: {integrity: sha512-w0+70CvTmuqbskWfzeYhn0IXxllr6mU+IeM2MU0M+j9OW64jkrvqY+pYFWrUnIIC9bEdij3NICruicwd5EgUuQ==} + engines: {node: '>= 18'} + peerDependencies: + '@intlify/shared': ^9.0.0 || ^10.0.0 || ^11.0.0 + '@vue/compiler-dom': ^3.0.0 + vue: 3.5.38 + vue-i18n: ^9.0.0 || ^10.0.0 || ^11.0.0 + peerDependenciesMeta: + '@intlify/shared': + optional: true + '@vue/compiler-dom': + optional: true + vue: + optional: true + vue-i18n: + optional: true + '@ioredis/commands@1.10.0': resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} @@ -2381,6 +2633,11 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@lunariajs/core@0.1.1': + resolution: {integrity: sha512-sAqM9+DVsLe3xHM9wu2pEnKGYMs/bWS9qpR+CGHol3RihOELnOQTzHddXbdB1MtgesbI8dnQuG64Ocd8KkWsng==} + engines: {node: '>=18.17.0'} + hasBin: true + '@mapbox/node-pre-gyp@2.0.3': resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} engines: {node: '>=18'} @@ -2498,6 +2755,11 @@ packages: '@opentelemetry/api': optional: true + '@miyaneee/rollup-plugin-json5@1.2.0': + resolution: {integrity: sha512-JjTIaXZp9WzhUHpElrqPnl1AzBi/rvRs065F71+aTmlqvTMVkdbjZ8vfFl4nRlgJy+TPBw69ZK4pwFdmOAt4aA==} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 + '@mongodb-js/saslprep@1.4.12': resolution: {integrity: sha512-QAfAMwNgnYxZ2C6D1HgeP7Gc4i/uvJRim415PCIL9ptRxWMNbWeLBYb2/9R4pGKny/s1FVu2JA2cxCUBUOggrA==} @@ -3000,6 +3262,10 @@ packages: '@nuxtjs/html-validator@2.1.0': resolution: {integrity: sha512-ldo8ioSsH3OEumtgwDMokTxlhjgO9FxjJWViAxisq5l/wjvaVX8SYTQ02wjtQcQQPSvS6BwgypAp400RlyFHng==} + '@nuxtjs/i18n@10.4.1': + resolution: {integrity: sha512-4fQWCNKF8+3s4+5OjBZqD20FkO2UpWhMWg43BO3d9FWlFcUKtb065lj1gPK1VjTm6omvKw+6W3d91i1OpbYrBw==} + engines: {node: '>=20.11.1'} + '@nuxtjs/mdc@0.22.1': resolution: {integrity: sha512-+tkGhBNxapWZiadZaf9yTxMtnSshjPY2VVnZsy/YoOu+c1p2S6MQgAHB9QWk+FIPT0epJ/VAtGh6qW1tjyPXOg==} @@ -3268,6 +3534,12 @@ packages: cpu: [arm] os: [android] + '@oxc-parser/binding-android-arm-eabi@0.128.0': + resolution: {integrity: sha512-aca6ZvzmCBUGOANQRiRQRZuRKYI3ENhcit6GisnknOOmcezfQc7xJ4dxlPU7MV7mOvrC7RNR1u3LAD7xyaiCxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-parser/binding-android-arm-eabi@0.129.0': resolution: {integrity: sha512-sG37CfXLlYXdDrggAFO/mKcO4w36piwf862xAZXIuf3nzKhWK1FvW4dqie+06++z+mDto2QeOQSvhyzBeK5jsQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3310,6 +3582,12 @@ packages: cpu: [arm64] os: [android] + '@oxc-parser/binding-android-arm64@0.128.0': + resolution: {integrity: sha512-BbeDmuohoJ7Rz/it5wnkj69i/OsCPS3Z51nLEzwO/Y6YshtC4JU+15oNwhY8v4LRKRYclRc7ggOikwrsJ/eOEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-parser/binding-android-arm64@0.129.0': resolution: {integrity: sha512-DVyLFN2+S0VOhT6lm5++tFqlu3x2Njiby6y5DhTzjV5uRsZWpifsBn6+yjtwAxl105peEjs5BHE3ToBJuQjLTg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3352,6 +3630,12 @@ packages: cpu: [arm64] os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.128.0': + resolution: {integrity: sha512-tRUHPt80417QmvNpoSslJT1VY8NUbWdrWR+L14Zn+RbOTcaqB8E6PYE/ZGN8jjWBzqporiA/H4MfO50ew/NCNA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.129.0': resolution: {integrity: sha512-QeqThtB8qax4IL+NFBWgshudyKkj5c076L8vyd8PCEx7U1wHyIbH49MEQ5J5iURFhUW5jiFmdnLKEwyOo0GAJA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3394,6 +3678,12 @@ packages: cpu: [x64] os: [darwin] + '@oxc-parser/binding-darwin-x64@0.128.0': + resolution: {integrity: sha512-rWI2Hb1Nt3U/vKsjyNvZzDC8i/l144U20DKjhzaTmwIhIiSRGeroPWWiImwypmKLqrw8GuIixbWJkpGWLbkzrQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-parser/binding-darwin-x64@0.129.0': resolution: {integrity: sha512-zn5+7nv4DlK4uFgblmhKm6xRV0QUHXOHyIDkjmhxJ53xSA9ahkb3pHNiHesNPXn/nK/VWU+C+Z6JYHdatZBh7g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3436,6 +3726,12 @@ packages: cpu: [x64] os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.128.0': + resolution: {integrity: sha512-hhpdVMaNCLgQxjgNPeeFzSeJMmZPc5lKfv0NGSI3egZq9EdnEGqeC8JsYsQjK7PoQgbvZ17xlj0SO5ziH5Obkg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.129.0': resolution: {integrity: sha512-SPTcDBiHWlgRpWFC1jnoi0sBWqCw4DFR+4b8+dV+NAhUu2ONERWyIVIOCfcE9a8BlvZsDCuXf3l/x7wQUs1Rsw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3478,6 +3774,12 @@ packages: cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.128.0': + resolution: {integrity: sha512-093zNw0zZ/e/obML+rhlSdmnzR0mVZluPcAkxunEc5E3F0yBVsFn24Y1ILfsEte11Ud041qn/gp2OJ1jxNqUng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.129.0': resolution: {integrity: sha512-Rgc9+WNKLbc+chyDTXyyJ7gbgLo+ve27CrRnmIwGgucGflrBZbutge5jdPPegcgf46RrR4dkBbMCp0/x16mdig==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3520,6 +3822,12 @@ packages: cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.128.0': + resolution: {integrity: sha512-fq7DmKmfC+dvD97IXrgbph6Jzwe0EDu+PYMofmzZ6fv5X1k9vtaqLpDGMuICO9MmUnyKAQmVl+wIv2RNy4Dz8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.129.0': resolution: {integrity: sha512-YtSsJ51VysXqlO8Cs2mWTyXvxBRemTHj4WDQjXwKl0SAxh+CVrEdXrdH+RnjxLj3JCUMFeYuHs5c+/DImfbKkg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3563,6 +3871,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.128.0': + resolution: {integrity: sha512-Xvm48jJah8TlIrURIjNOP/gNiGe6aKvCB+r06VliflFo8Kq7VOLE8PxtgShJzZIqubrgdMdYfvuPPozn7F6MbQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.129.0': resolution: {integrity: sha512-9oK8iQr9KtgI5JhaJ+5IwiQsXEo6NuasFgovtJGrdK/RxbA0bO4YKRvVY7M+8lozUCVz1U7XrFFODv3emIOPRA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3612,6 +3927,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.128.0': + resolution: {integrity: sha512-M7iwBGmYJTx+pKOYFjI0buop4gJvlmcVzFGaXPt21DKpQkbQZG1f63Yg7LloIYT/t9yLxCw0Lhfx/RFlAlMSjA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.129.0': resolution: {integrity: sha512-GghE/bf9ZqgqZFxLacgP0ImVD6UiLKQOpvpgUoIsqjopu2ms/+p1L0d0Dv2Sck+8p0FbKS2WE3IjqmIlLbxJgA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3661,6 +3983,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.128.0': + resolution: {integrity: sha512-21LGNIZb1Pcfk5/EGsqabrxv4yqQOWis1407JJrClS7XpFCrbvr74YAB1V+m54cYbwvO6UWwQqS4WecxiyfCRg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.129.0': resolution: {integrity: sha512-A2PW0UbERzKGV6fKX1zoe2Tkc1zVcEJSSPW9IUSKbZAPuPe+M5/5hTA+6fQbWmevabe2B3IDky66a1lFGjpBKA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3710,6 +4039,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.128.0': + resolution: {integrity: sha512-gyHjOTFpg9bTTYjxPmQirvufb89+VdZwVfcMtAUyPr6F5H8ZswvCQshK4qOW+Q+2Xyb33hduRgY/eFHJQjU/vQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.129.0': resolution: {integrity: sha512-omwxd9H+jrl1T72RI666k4ho7Eli2iHdELzf+dL0D+uXThNZXYJCbKjm5rK2hrHmDy4O+NWv7+khBrEkorLsgw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3759,6 +4095,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.128.0': + resolution: {integrity: sha512-X6Q2oKUrP5GyDd2xniuEBLk6aFQCZ97W2+aVXGgJXdjx5t4/oFuA9ri0wLOUrBIX+qdSuK581snMBio4z910eA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.129.0': resolution: {integrity: sha512-v2hi8id+M8C0uY8uuG2t2a5vr8H9XyHXiHL12yMdMNtgn04nnM/8hlOGuoJuxVc07PhClNiaoSaY2eXehSRa7w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3808,6 +4151,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.128.0': + resolution: {integrity: sha512-BdzTmqxfxoYkpgokoLaSnOX6T+R3/goL42klre2tnG+kHbG2TXS0VN+P5BPofH1axdKOHy5ei4ENZrjmCOt2lA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.129.0': resolution: {integrity: sha512-UXrdDyLh1Obgj5X+IVVXWoo5/FJbFsU8/uLQ/M9lkVUwBUKpRFxNEhzwBNv21qqdKgAh+pr2CCVD8J1JfRPsIQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3857,6 +4207,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.128.0': + resolution: {integrity: sha512-OO1nW2Q7sSYYvJZpDHdvyFSdRaVcQqRijZSSmWVMqFxPYy8cEF45zJ9fcdIYuzIT3jYq6YRhEFm/VMWNWhE22Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.129.0': resolution: {integrity: sha512-hsL/3/kdX9FGLqOj8DR3Eki4Y6zO1i3+ZHhiPwX0hDt4n+18abkfUzePCv3h8SShprwCmwdxPnlrebZ5+MZ+cw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3906,6 +4263,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.128.0': + resolution: {integrity: sha512-4NehAe404MRdoZVS9DW8C5XbJwbXIc/KfVlYdpi5vE4081zc9Y0YzKVqyOYj/Puye7/Do+ohaONBFWlEHYl9hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.129.0': resolution: {integrity: sha512-kdXvJ4crOeRld3vWl0J0VU4nmnT4pZ3lKGA5tZ1y0UPWsBtElDYd+jsz4lE36tpAbCiWm0M0PG0laUNBSE+Wlw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3954,6 +4318,12 @@ packages: cpu: [arm64] os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.128.0': + resolution: {integrity: sha512-kVbqgW9xLL8bh8oc7aYOJilRKXE5G33+tE0jan+duo/9OriaFRpijcCwT2waWs2oqYROYq0GlE7/p3ywoshVeg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.129.0': resolution: {integrity: sha512-DusJfcK7EGwf9TEakB+z6SXCLdHGvDZ8U8882bzWb4oVrORHpbkFl9npS7cN3YC2axcVKoktbxZevS1nxVCKFw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3995,6 +4365,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@oxc-parser/binding-wasm32-wasi@0.128.0': + resolution: {integrity: sha512-L38ojghJYHmgiz6fJd7jwLB/ESDBpB02NdFxh+smqVM6P2anCEvHn0jhaSrt5eVNR1Ak8+moOeftUlofeyvniA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@oxc-parser/binding-wasm32-wasi@0.129.0': resolution: {integrity: sha512-Iie9CcII+ELSinKFnxTR15xhI9qriVivEhbFh3driRNbzms/5ioDAU0fwe8Mf1FEaz3n2FtiUVX0h0nwKLYk0A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4031,6 +4406,12 @@ packages: cpu: [arm64] os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.128.0': + resolution: {integrity: sha512-xgvO35GyHBtjlQ5AEpaYr7Rll1rvY7zqIhT6ty8E3ezBW2J1SFLjIDEvI/tcgDg6oaseDAqVcM+jU1HuCekgZw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.129.0': resolution: {integrity: sha512-99kH1udLyrts+wGm+u0VhPbogkb2wxc/6J1XMKOpS6Kx5DjBWGRZZfBjfCGI3xKSInpYbZn4TLWLX1Q1GURYwg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4073,6 +4454,12 @@ packages: cpu: [ia32] os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.128.0': + resolution: {integrity: sha512-OY+3eM2SN72prHKRB22mPz8o5A/7dJ+f5DFLBVvggyZhEaNDAH9IB+ElMjmOkOIwf5MDCUAowCK7pAncNxzpBA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.129.0': resolution: {integrity: sha512-tmSBR1A4yH697qV291xKyDe4OAWFchJ+cXf2wuipx/vK3n5d5Ej9MVLRtXlIcZ38n8qAjsF0/AnskaYgxM151A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4115,6 +4502,12 @@ packages: cpu: [x64] os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.128.0': + resolution: {integrity: sha512-NE9ny+cPUCCObXa0IKLfj0tCdPd7pe/dz9ZpkxpUOymB3miNeMPybdlYYTBSGJUalMWeBM85/4JcCErCNTqOXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.129.0': resolution: {integrity: sha512-Z1PbJvkPeLASIUxa3AnrQ5H+vv1K9zC0IGnQqoKfM0ZvsvCSe0d3u5m7d9iuy+HB7GrcElHuwKb0d0qFdtG0iA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4161,6 +4554,9 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.128.0': + resolution: {integrity: sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==} + '@oxc-project/types@0.129.0': resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} @@ -4282,48 +4678,97 @@ packages: cpu: [x64] os: [win32] + '@oxc-transform/binding-android-arm-eabi@0.128.0': + resolution: {integrity: sha512-qVO4izEs88ZSo7KOK4P+O5nAXXJmkSadInvFjGkhVnm2R2Wr8trU/GLhjAK0S0u8Qv9bkXspNhgpECk+CTQ/ew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-transform/binding-android-arm-eabi@0.133.0': resolution: {integrity: sha512-2A79NBpyBKgHJ0FwgC8D1hzp3x2ujyvqq/kG+M76YyDMMkxLhX6A3vjnAnfEKycOoZxuKhwYu8BF9hKq67ykIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] + '@oxc-transform/binding-android-arm64@0.128.0': + resolution: {integrity: sha512-F3RXlbCzIgkpRWlz1PEguDZl5NzZRmbeHKTFTQWFnK6mIdw2EkWihPVv9+CIcO80c7+sF/YRGOBaji6hwUDhtQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-transform/binding-android-arm64@0.133.0': resolution: {integrity: sha512-dynEph/hyoSgBzd2XbNlW37NK97nU6tZMs5jrhObUxSasBV/Gv9THZrWj9AlbWiMXR07WFYE82C9axjntYyBSw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] + '@oxc-transform/binding-darwin-arm64@0.128.0': + resolution: {integrity: sha512-xj63gIzQ67LDYHCOWXSHgfx4LbPVz1ck0G3y0eR6mbgYk3CwwylbhWi/CaDC6BWsHwoLQryeYjHB5XBCR0EPMQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-transform/binding-darwin-arm64@0.133.0': resolution: {integrity: sha512-4hGgKOG+dZSN3xjcgNWpcihekRG7/YbbAdjyz07yv0HjzA6kdqYAhGrn84374UPO2h6etYJwsCBoM9iJHHvJ8w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@oxc-transform/binding-darwin-x64@0.128.0': + resolution: {integrity: sha512-YQkvFqNqpwEt197RjREAOWeRANalPtCD+ayZlx4IjTQ6IOYZEP83B9/++gTQisHV3r8E7dU8UqJKeSS1cHlTQg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-transform/binding-darwin-x64@0.133.0': resolution: {integrity: sha512-7J11/9PFkznmKuANkCAjt3znV1BcDFXQSgDiBvDxXT3Wm6995/zxrJD5zmo+5XSgY4sm+2V8/ED6ZSD3mKOC5A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@oxc-transform/binding-freebsd-x64@0.128.0': + resolution: {integrity: sha512-Jvd3Ximb3x3o0+xRBB5lq63JlzxhJN787IsBjn0PEnmuocYQj+tJ5BB4n9xPIG27GXwg3ycckQPO/RsWeEcBPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-transform/binding-freebsd-x64@0.133.0': resolution: {integrity: sha512-5EMAO0vzCpUfhn6aSjIUeJeRI2ztevHwSVr/M8sZ2VBYc79UuOfjjMCQ67LtUbgpvQtpBWkzeAHCP3L7JFYmlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@oxc-transform/binding-linux-arm-gnueabihf@0.128.0': + resolution: {integrity: sha512-TaRKWeGnAJNIdCa5+m0I8/SksBgkLX94iH40qy3chvLuaIOGAmOViUStYx8geXBzO9P99V7En8nHXLoqCONBRQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-transform/binding-linux-arm-gnueabihf@0.133.0': resolution: {integrity: sha512-z6XT8tmo9sPmCIYaFIxDelBU4wXLwwWMX2VNCMIY6bkQp5r+kRtVXYS3yLbJHMKEhRKvw/g+Z7fO9aadsGGEAw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-transform/binding-linux-arm-musleabihf@0.128.0': + resolution: {integrity: sha512-7TMrtA5/3SCvS+yMPrGnri5T4ZhIoCbjwKWV6Kn8d3v+vx7MpEmNkfe+CdF3rb5LlnuxeDMPwr1E2ntya0b8HQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-transform/binding-linux-arm-musleabihf@0.133.0': resolution: {integrity: sha512-GQDpEV2VhHG8hT5BviDv+emi9oHYhfv+JJJWROYp+eGgWjiQMp4QZVb6Bu3kwVMzkwy0r200ToA1KThYTq53ug==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-transform/binding-linux-arm64-gnu@0.128.0': + resolution: {integrity: sha512-lMQEa1jLBNm1N+5uvyj9zX9urVY4xKkLnhO8/4CtSGdXX+mExqsVawyQPAZqbtq1fLQ0yt1QYJ9YuM0+fiSJTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-transform/binding-linux-arm64-gnu@0.133.0': resolution: {integrity: sha512-VstR+NEQAJb80ysWk2vPjEvg0JzwEjKn2hDbC/joa5zGXkCnVVCWgAGG8c6o23S981a7XRpCMcClBgeD1q9H2A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4331,6 +4776,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-transform/binding-linux-arm64-musl@0.128.0': + resolution: {integrity: sha512-dPSjyd0gQ9dE3mpdJi0BHNJaqQz4V7mVW6Fbs6jRSiGnrxwGfXdMJFInXoJ49B3k5Zhfa9Is9Ixp6St7c6ouCA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-transform/binding-linux-arm64-musl@0.133.0': resolution: {integrity: sha512-Ec7xJdDrnukgiz20E3iDNzAIgx1XXn8cVVsNNUpgEIAvNlXZaocqlQT8Zalk0Lv3fbkxcJ+9BuWB0ndBRHQtzg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4338,6 +4790,13 @@ packages: os: [linux] libc: [musl] + '@oxc-transform/binding-linux-ppc64-gnu@0.128.0': + resolution: {integrity: sha512-YNa9XAotPKvAXFJrHC7kBsHMVg0HOB4vRiKuYUjzFsfLkxTbuztKHTKG/gW5kjp7dBw+TNFofTaVCVZgOnHXPQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-transform/binding-linux-ppc64-gnu@0.133.0': resolution: {integrity: sha512-6YX38grimcigz20eYpyz6e4c9rDKzwK3i+tcDpgwYj0bWreaAOwrABmSmKplPJOorkDVlbT69wPCN+d11irBQw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4345,6 +4804,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-transform/binding-linux-riscv64-gnu@0.128.0': + resolution: {integrity: sha512-jjSiG9H8ya/U3igW5DdIBFIDwhffF7Vbc7th2tcHV73eg0DQz75n36a9RmQ1/0aS9vknUuNtY6SODr8/gmuzsQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-transform/binding-linux-riscv64-gnu@0.133.0': resolution: {integrity: sha512-WxMIzItRJR66lgaAyyqj0FFwLMpcuCV9mTFcUMQpIz8+Hey1Enk8xuv+7QpSsqCR5zRlwNr092dsFkz5cbvtrw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4352,6 +4818,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-transform/binding-linux-riscv64-musl@0.128.0': + resolution: {integrity: sha512-FVUr/XNT7BfQA4XVMel/HTCJi5mQyEitslgX42ztYPnCFMRFG1sQQKgnlLJdl7qifuyxpvKLR1f7h7HEuwWw1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-transform/binding-linux-riscv64-musl@0.133.0': resolution: {integrity: sha512-+x6dnO87986rjVNjcF0tg8wVS0e/SH8nzLa/X0Wsh7jtEniN7buvR8iqZm8pnsfaZ8DH5F4GCSZpoPRrd9jJ6w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4359,6 +4832,13 @@ packages: os: [linux] libc: [musl] + '@oxc-transform/binding-linux-s390x-gnu@0.128.0': + resolution: {integrity: sha512-caJnVw5PG8v339zAyHgA7p34xXa3A4Kc9VyrDgsT1znr51qacaUv4BRlgRi0qkqxRWXYNVFfsbU2g0t1qS7E9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-transform/binding-linux-s390x-gnu@0.133.0': resolution: {integrity: sha512-oEyQudXIwWM/+v0vZzPbAi25YMWyvjtQYYjuSrhMEQwe7ZEMDXscX7U1j6alrVdZq2DtCMeror3X/Dv7p/JUwg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4366,6 +4846,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-transform/binding-linux-x64-gnu@0.128.0': + resolution: {integrity: sha512-zkQKjsHEUX3ckQBcZTtHE/7pgFMkWQp6y/4t7N8eT3j8wnoL+vapv7l4ISjgx1/EePRJN1HErYXmriz7tPVKRg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-transform/binding-linux-x64-gnu@0.133.0': resolution: {integrity: sha512-G8P/OadKTbyUHz5TK63sDDtUHwn2SXG/o0oGo4GGTzBu70xmUSN5/ZUgpyl6ypAmbshoyw8nC7+msb3BjzHxaA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4373,6 +4860,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-transform/binding-linux-x64-musl@0.128.0': + resolution: {integrity: sha512-NjYtwl9ijp34iisHxYBvE7nii1Ac0QPP3doHv8MQHhDA3zjUcDCROuBNybfaEYCxnJ1aF+cAPqsyeopnAGsyuQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-transform/binding-linux-x64-musl@0.133.0': resolution: {integrity: sha512-Oi/fyOzZ+aytmmsRND5pGgvux4n++v9cG4qNFiXj7qFwSqBKWZHBq7cJLXqbH1I81pyI3kvU1Za+1qk3afXuwg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4380,29 +4874,58 @@ packages: os: [linux] libc: [musl] + '@oxc-transform/binding-openharmony-arm64@0.128.0': + resolution: {integrity: sha512-itsi0tVkVdrYphSppdFChLq9tD0pvbRRS3EV8NQYKZ/NWHMoxzjlf9TFA/ZZYV113juYo1Dq3glVX48knhBeFQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-transform/binding-openharmony-arm64@0.133.0': resolution: {integrity: sha512-/ZElgq+/tcga27X2G2AUpxcYX0baX94Gz658w6Zz2P+6Kr06bfYSrdtC0P7oPrbu3Gy/6kpiSoJPgZy8R2IjYQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@oxc-transform/binding-wasm32-wasi@0.128.0': + resolution: {integrity: sha512-elzjX2gy1jcseeFaKtbk/6T2FPTpGNx0IpeD0iyk6cahWN7wD6eHY5u7th1X85cYbRq9rqniS+xYIxN3StthWg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@oxc-transform/binding-wasm32-wasi@0.133.0': resolution: {integrity: sha512-GANcoEa8Nzza7saxdb4qWO24U6jk4nK6G+g87lGp8TTU45CUvWf1Igdze2+NrebgiwOy6F1/h6Esag4DM3JTtQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@oxc-transform/binding-win32-arm64-msvc@0.128.0': + resolution: {integrity: sha512-p5LmbI66dk2dziJSUzjQ24gOWeI6pJpXcOC6famloRtKCq54V5/KegsztFgZZCtYFEAEqFgcfspFHrV+CcKWcg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-transform/binding-win32-arm64-msvc@0.133.0': resolution: {integrity: sha512-2+uDo/+ZvGQu10J8xryg/l5PdBt2vXPtf+0aIosVKJavqCaKcBDdo95OUaEulx0bqvoytAQ4yyz2gcPZ40mjcQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@oxc-transform/binding-win32-ia32-msvc@0.128.0': + resolution: {integrity: sha512-CMU3Yn05rXeLw7GyVlDB3bbp2iV14yt3VWyF0pNuMK9NVgOmUkXgFLe5SOcX9rEm64TRJjOMEghtE5+r0GtqIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-transform/binding-win32-ia32-msvc@0.133.0': resolution: {integrity: sha512-zpPIZ1S3JHmSEFyyGyPYCwhOiNLyfaPifYxK8BQY21JXyHglu/wUr3/ESFrXb+XegEy/iBlWbzr3FzPtcq1MUw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] + '@oxc-transform/binding-win32-x64-msvc@0.128.0': + resolution: {integrity: sha512-Vck5AdNH2JPYMQWVDxvX5PbDFfqVG+tCOgKJzAC/S9bgbD3qcMjN5Dx6FOmEbwY3hZm//fzOsY4tErofoiK/aQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-transform/binding-win32-x64-msvc@0.133.0': resolution: {integrity: sha512-cADrfLvc/VeyvpvQS+t5ktqfyqyyGANZC5NHp++JAElacfXqq/+k8bYkjqMWzNZ3HxkJtL1qDHfZZCA9+4hlSQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5157,6 +5680,15 @@ packages: rollup: optional: true + '@rollup/plugin-yaml@4.1.2': + resolution: {integrity: sha512-RpupciIeZMUqhgFE97ba0s98mOFS7CWzN3EJNhJkqSv9XLlWYtwVdtE6cDw6ASOF/sZVFS7kRJXftaqM2Vakdw==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/pluginutils@5.4.0': resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} engines: {node: '>=14.0.0'} @@ -6271,6 +6803,10 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@8.64.0': + resolution: {integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.62.1': resolution: {integrity: sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6281,6 +6817,10 @@ packages: resolution: {integrity: sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.64.0': + resolution: {integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.62.1': resolution: {integrity: sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6291,6 +6831,10 @@ packages: resolution: {integrity: sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.64.0': + resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.2': resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==} @@ -7618,6 +8162,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + comment-parser@1.4.7: resolution: {integrity: sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==} engines: {node: '>= 12.0.0'} @@ -8107,6 +8655,10 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dot-object@2.1.5: + resolution: {integrity: sha512-xHF8EP4XH/Ba9fvAF2LDd5O3IITVolerVV6xvkxoM8zlGEiCUrggpAnHyOoKJKCrhvPcGATFAUwIujj7bRG5UA==} + hasBin: true + dot-prop@10.1.0: resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==} engines: {node: '>=20'} @@ -8421,6 +8973,11 @@ packages: es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.27.7: resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} @@ -8499,6 +9056,10 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -9027,6 +9588,11 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} @@ -9654,6 +10220,10 @@ packages: is-utf8@0.2.1: resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} + is-valid-glob@1.0.0: + resolution: {integrity: sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==} + engines: {node: '>=0.10.0'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -9723,6 +10293,10 @@ packages: engines: {node: '>=10'} hasBin: true + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -9819,6 +10393,10 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-eslint-parser@2.4.2: + resolution: {integrity: sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} @@ -10721,6 +11299,9 @@ packages: nuxt-csurf@1.6.6: resolution: {integrity: sha512-T2WTlZQeTuoLCtoRJVlpBABEEx5vNPzHjIow/7BoQqoZ6S7YYYXAT1smKv7t6uvDfK7BZDhfLPQHDomUplg1Sw==} + nuxt-define@1.0.0: + resolution: {integrity: sha512-CYZ2WjU+KCyCDVzjYUM4eEpMF0rkPmkpiFrybTqqQCRpUbPt2h3snswWIpFPXTi+osRCY6Og0W/XLAQgDL4FfQ==} + nuxt-link-checker@5.1.2: resolution: {integrity: sha512-qnRRT0suYq3xTf4EGQbo0Dx0LShVOMTa3WTULJhhvn/WkpbC95uvDKQAX+/JCME3EPob4ADiy8PPTqsc74Z/ig==} peerDependencies: @@ -11042,6 +11623,10 @@ packages: resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.128.0: + resolution: {integrity: sha512-XkOw3eiIxAgQ19WRew/Bq9wc5Ga/guaWIzDBzq80z1PyuDNGvWBpPby9k6YGwV8A8uMw+Nlq3xqlzuDYmUFYUw==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.129.0: resolution: {integrity: sha512-S6eFI+VLkpyA+/Lf8z6qURjDV6Mgo74SLNznNopHTlQW3hedv2MB/z31kBRuBCCTqZN9HHdva0ojljEhPnBKFA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -11069,10 +11654,19 @@ packages: oxc-resolver@11.23.0: resolution: {integrity: sha512-f0+l598CJMOLnYPXsXxttJALH0ljtivdRMKtvHhxRuWa5FYmw5+qODARl8oYjMC/brpzKcrpdORsOBrTqhBZ9A==} + oxc-transform@0.128.0: + resolution: {integrity: sha512-8DfEHlmUiLOHlCK9DGX+d5tORc1xwPPvoRSHSJCYgLHyGjKp4PvfBrvgi59DkEW0SMOWfO8GL9t+R7vdKtupbg==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-transform@0.133.0: resolution: {integrity: sha512-9lt2b+hkG6yqe0fUDMHhMk7rgI9uTjNxU9wauQiYnHzc4kZI8JP/OhBqXTIJQTrqRJ8CkSH3O5AhQ13ke28yNg==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-walker@0.7.0: + resolution: {integrity: sha512-54B4KUhrzbzc4sKvKwVYm7E2PgeROpGba0/2nlNZMqfDyca+yOor5IMb4WLGBatGDT0nkzYdYuzylg7n3YfB7A==} + peerDependencies: + oxc-parser: '>=0.98.0' + oxc-walker@1.0.0: resolution: {integrity: sha512-eMsHflAGfOskpWxtp9xP/f5b96XLEU8ifTd2gOOCkdux9HMxKGy5S1ru0Gh1B3aPu+YbfmWUUVkcb7MrZz3XyQ==} peerDependencies: @@ -12918,6 +13512,10 @@ packages: tomlify-j0.4@3.0.0: resolution: {integrity: sha512-2Ulkc8T7mXJ2l0W476YC/A209PR38Nw8PuaCNtk9uI3t1zzFdGQeWYGQvmj2PZkVvRC/Yoi4xQKMRnWc/N29tQ==} + tosource@2.0.0-alpha.3: + resolution: {integrity: sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==} + engines: {node: '>=10'} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -13065,6 +13663,9 @@ packages: ultrahtml@1.6.0: resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + ultramatter@0.0.4: + resolution: {integrity: sha512-1f/hO3mR+/Hgue4eInOF/Qm/wzDqwhYha4DxM0hre9YIUyso3fE2XtrAU6B4njLqTC8CM49EZaYgsVSa+dXHGw==} + unagent@0.0.8: resolution: {integrity: sha512-xIZpO1mnllQRAdgI3Ibr0gNdkcNs6d6oQDSpDGEbycv/KYCDopH1+hD5tFOwHRXiqeMyQxbPPHcnkpBgzm4QYA==} peerDependencies: @@ -13657,6 +14258,16 @@ packages: peerDependencies: vue: 3.5.38 + vue-i18n-extract@2.0.7: + resolution: {integrity: sha512-i1NW5R58S720iQ1BEk+6ILo3hT6UA8mtYNNolSH4rt9345qvXdvA6GHy2+jHozdDAKHwlu9VvS/+vIMKs1UYQw==} + hasBin: true + + vue-i18n@11.4.6: + resolution: {integrity: sha512-l0gE7Rfy0phCa5ChKYkOq543Wgd39BCK6hkktfr1Ed4D99oRkgPK9ffShASZdeC8OJxGfdWmpYoAaAH6iLEuIg==} + engines: {node: '>= 22'} + peerDependencies: + vue: 3.5.38 + vue-inbrowser-compiler-independent-utils@4.71.1: resolution: {integrity: sha512-K3wt3iVmNGaFEOUR4JIThQRWfqokxLfnPslD41FDZB2ajXp789+wCqJyGYlIFsvEQ2P61PInw6/ph5iiqg51gg==} peerDependencies: @@ -13951,6 +14562,10 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml-eslint-parser@1.3.2: + resolution: {integrity: sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg==} + engines: {node: ^14.17.0 || >=16.0.0} + yaml@2.9.0: resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} @@ -15260,6 +15875,11 @@ snapshots: '@chevrotain/utils@10.5.0': {} + '@clack/core@0.3.5': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + '@clack/core@0.5.0': dependencies: picocolors: 1.1.1 @@ -15624,6 +16244,9 @@ snapshots: '@whatwg-node/promise-helpers': 1.3.2 tslib: 2.8.1 + '@esbuild/aix-ppc64@0.25.12': + optional: true + '@esbuild/aix-ppc64@0.27.7': optional: true @@ -15633,6 +16256,9 @@ snapshots: '@esbuild/aix-ppc64@0.28.1': optional: true + '@esbuild/android-arm64@0.25.12': + optional: true + '@esbuild/android-arm64@0.27.7': optional: true @@ -15642,6 +16268,9 @@ snapshots: '@esbuild/android-arm64@0.28.1': optional: true + '@esbuild/android-arm@0.25.12': + optional: true + '@esbuild/android-arm@0.27.7': optional: true @@ -15651,6 +16280,9 @@ snapshots: '@esbuild/android-arm@0.28.1': optional: true + '@esbuild/android-x64@0.25.12': + optional: true + '@esbuild/android-x64@0.27.7': optional: true @@ -15660,6 +16292,9 @@ snapshots: '@esbuild/android-x64@0.28.1': optional: true + '@esbuild/darwin-arm64@0.25.12': + optional: true + '@esbuild/darwin-arm64@0.27.7': optional: true @@ -15669,6 +16304,9 @@ snapshots: '@esbuild/darwin-arm64@0.28.1': optional: true + '@esbuild/darwin-x64@0.25.12': + optional: true + '@esbuild/darwin-x64@0.27.7': optional: true @@ -15678,6 +16316,9 @@ snapshots: '@esbuild/darwin-x64@0.28.1': optional: true + '@esbuild/freebsd-arm64@0.25.12': + optional: true + '@esbuild/freebsd-arm64@0.27.7': optional: true @@ -15687,6 +16328,9 @@ snapshots: '@esbuild/freebsd-arm64@0.28.1': optional: true + '@esbuild/freebsd-x64@0.25.12': + optional: true + '@esbuild/freebsd-x64@0.27.7': optional: true @@ -15696,6 +16340,9 @@ snapshots: '@esbuild/freebsd-x64@0.28.1': optional: true + '@esbuild/linux-arm64@0.25.12': + optional: true + '@esbuild/linux-arm64@0.27.7': optional: true @@ -15705,6 +16352,9 @@ snapshots: '@esbuild/linux-arm64@0.28.1': optional: true + '@esbuild/linux-arm@0.25.12': + optional: true + '@esbuild/linux-arm@0.27.7': optional: true @@ -15714,6 +16364,9 @@ snapshots: '@esbuild/linux-arm@0.28.1': optional: true + '@esbuild/linux-ia32@0.25.12': + optional: true + '@esbuild/linux-ia32@0.27.7': optional: true @@ -15723,6 +16376,9 @@ snapshots: '@esbuild/linux-ia32@0.28.1': optional: true + '@esbuild/linux-loong64@0.25.12': + optional: true + '@esbuild/linux-loong64@0.27.7': optional: true @@ -15732,6 +16388,9 @@ snapshots: '@esbuild/linux-loong64@0.28.1': optional: true + '@esbuild/linux-mips64el@0.25.12': + optional: true + '@esbuild/linux-mips64el@0.27.7': optional: true @@ -15741,6 +16400,9 @@ snapshots: '@esbuild/linux-mips64el@0.28.1': optional: true + '@esbuild/linux-ppc64@0.25.12': + optional: true + '@esbuild/linux-ppc64@0.27.7': optional: true @@ -15750,6 +16412,9 @@ snapshots: '@esbuild/linux-ppc64@0.28.1': optional: true + '@esbuild/linux-riscv64@0.25.12': + optional: true + '@esbuild/linux-riscv64@0.27.7': optional: true @@ -15759,6 +16424,9 @@ snapshots: '@esbuild/linux-riscv64@0.28.1': optional: true + '@esbuild/linux-s390x@0.25.12': + optional: true + '@esbuild/linux-s390x@0.27.7': optional: true @@ -15768,6 +16436,9 @@ snapshots: '@esbuild/linux-s390x@0.28.1': optional: true + '@esbuild/linux-x64@0.25.12': + optional: true + '@esbuild/linux-x64@0.27.7': optional: true @@ -15777,6 +16448,9 @@ snapshots: '@esbuild/linux-x64@0.28.1': optional: true + '@esbuild/netbsd-arm64@0.25.12': + optional: true + '@esbuild/netbsd-arm64@0.27.7': optional: true @@ -15786,6 +16460,9 @@ snapshots: '@esbuild/netbsd-arm64@0.28.1': optional: true + '@esbuild/netbsd-x64@0.25.12': + optional: true + '@esbuild/netbsd-x64@0.27.7': optional: true @@ -15795,6 +16472,9 @@ snapshots: '@esbuild/netbsd-x64@0.28.1': optional: true + '@esbuild/openbsd-arm64@0.25.12': + optional: true + '@esbuild/openbsd-arm64@0.27.7': optional: true @@ -15804,6 +16484,9 @@ snapshots: '@esbuild/openbsd-arm64@0.28.1': optional: true + '@esbuild/openbsd-x64@0.25.12': + optional: true + '@esbuild/openbsd-x64@0.27.7': optional: true @@ -15813,6 +16496,9 @@ snapshots: '@esbuild/openbsd-x64@0.28.1': optional: true + '@esbuild/openharmony-arm64@0.25.12': + optional: true + '@esbuild/openharmony-arm64@0.27.7': optional: true @@ -15822,6 +16508,9 @@ snapshots: '@esbuild/openharmony-arm64@0.28.1': optional: true + '@esbuild/sunos-x64@0.25.12': + optional: true + '@esbuild/sunos-x64@0.27.7': optional: true @@ -15831,6 +16520,9 @@ snapshots: '@esbuild/sunos-x64@0.28.1': optional: true + '@esbuild/win32-arm64@0.25.12': + optional: true + '@esbuild/win32-arm64@0.27.7': optional: true @@ -15840,6 +16532,9 @@ snapshots: '@esbuild/win32-arm64@0.28.1': optional: true + '@esbuild/win32-ia32@0.25.12': + optional: true + '@esbuild/win32-ia32@0.27.7': optional: true @@ -15849,6 +16544,9 @@ snapshots: '@esbuild/win32-ia32@0.28.1': optional: true + '@esbuild/win32-x64@0.25.12': + optional: true + '@esbuild/win32-x64@0.27.7': optional: true @@ -16174,17 +16872,97 @@ snapshots: '@inquirer/figures@2.0.7': {} - '@inquirer/type@4.0.7(@types/node@24.12.4)': - optionalDependencies: - '@types/node': 24.12.4 + '@inquirer/type@4.0.7(@types/node@24.12.4)': + optionalDependencies: + '@types/node': 24.12.4 + + '@internationalized/date@3.12.2': + dependencies: + '@swc/helpers': 0.5.23 + + '@internationalized/number@3.6.7': + dependencies: + '@swc/helpers': 0.5.23 + + '@intlify/bundle-utils@11.2.4(vue-i18n@11.4.6)': + dependencies: + '@intlify/message-compiler': 11.4.6 + '@intlify/shared': 11.4.6 + acorn: 8.17.0 + esbuild: 0.25.12 + escodegen: 2.1.0 + estree-walker: 2.0.2 + jsonc-eslint-parser: 2.4.2 + source-map-js: 1.2.1 + yaml-eslint-parser: 1.3.2 + optionalDependencies: + vue-i18n: 11.4.6(vue@3.5.38) + + '@intlify/core-base@11.4.6': + dependencies: + '@intlify/devtools-types': 11.4.6 + '@intlify/message-compiler': 11.4.6 + '@intlify/shared': 11.4.6 + + '@intlify/core@11.4.6': + dependencies: + '@intlify/core-base': 11.4.6 + '@intlify/shared': 11.4.6 + + '@intlify/devtools-types@11.4.6': + dependencies: + '@intlify/core-base': 11.4.6 + '@intlify/shared': 11.4.6 + + '@intlify/h3@0.7.4': + dependencies: + '@intlify/core': 11.4.6 + '@intlify/utils': 0.13.0 + + '@intlify/message-compiler@11.4.6': + dependencies: + '@intlify/shared': 11.4.6 + source-map-js: 1.2.1 + + '@intlify/shared@11.4.6': {} + + '@intlify/unplugin-vue-i18n@11.2.4(@voidzero-dev/vite-plus-core@0.1.19)(@vue/compiler-dom@3.5.39)(eslint@9.39.4)(rollup@4.62.2)(typescript@6.0.3)(vue-i18n@11.4.6)(vue@3.5.38)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@intlify/bundle-utils': 11.2.4(vue-i18n@11.4.6) + '@intlify/shared': 11.4.6 + '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.4.6)(@vue/compiler-dom@3.5.39)(vue-i18n@11.4.6)(vue@3.5.38) + '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + '@typescript-eslint/scope-manager': 8.64.0 + '@typescript-eslint/typescript-estree': 8.62.1(typescript@6.0.3) + debug: 4.4.3 + fast-glob: 3.3.3 + pathe: 2.0.3 + picocolors: 1.1.1 + unplugin: 2.3.11 + vue: 3.5.38(typescript@6.0.3) + optionalDependencies: + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@24.12.4)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(typescript@6.0.3)(yaml@2.9.0)' + vue-i18n: 11.4.6(vue@3.5.38) + transitivePeerDependencies: + - '@vue/compiler-dom' + - eslint + - rollup + - supports-color + - typescript + + '@intlify/utils@0.13.0': {} - '@internationalized/date@3.12.2': - dependencies: - '@swc/helpers': 0.5.23 + '@intlify/utils@0.14.1': {} - '@internationalized/number@3.6.7': + '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.4.6)(@vue/compiler-dom@3.5.39)(vue-i18n@11.4.6)(vue@3.5.38)': dependencies: - '@swc/helpers': 0.5.23 + '@babel/parser': 7.29.7 + optionalDependencies: + '@intlify/shared': 11.4.6 + '@vue/compiler-dom': 3.5.39 + vue: 3.5.38(typescript@6.0.3) + vue-i18n: 11.4.6(vue@3.5.38) '@ioredis/commands@1.10.0': {} @@ -16237,6 +17015,21 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@lunariajs/core@0.1.1': + dependencies: + '@clack/core': 0.3.5 + fast-glob: 3.3.3 + get-port: 7.2.0 + jiti: 1.21.7 + micromatch: 4.0.8 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + simple-git: 3.36.0 + ultramatter: 0.0.4 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + '@mapbox/node-pre-gyp@2.0.3': dependencies: consola: 3.4.2 @@ -16349,6 +17142,12 @@ snapshots: - bufferutil - utf-8-validate + '@miyaneee/rollup-plugin-json5@1.2.0(rollup@4.62.2)': + dependencies: + '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + json5: 2.2.3 + rollup: 4.62.2 + '@mongodb-js/saslprep@1.4.12': dependencies: sparse-bitfield: 3.0.3 @@ -17794,6 +18593,73 @@ snapshots: - magicast - vitest + '@nuxtjs/i18n@10.4.1(@netlify/blobs@10.7.9)(@voidzero-dev/vite-plus-core@0.1.19)(@vue/compiler-dom@3.5.39)(db0@0.3.4)(esbuild@0.28.1)(eslint@9.39.4)(ioredis@5.11.1)(magicast@0.5.3)(rolldown@1.1.4)(rollup@4.62.2)(typescript@6.0.3)(vue@3.5.38)': + dependencies: + '@intlify/core': 11.4.6 + '@intlify/h3': 0.7.4 + '@intlify/shared': 11.4.6 + '@intlify/unplugin-vue-i18n': 11.2.4(@voidzero-dev/vite-plus-core@0.1.19)(@vue/compiler-dom@3.5.39)(eslint@9.39.4)(rollup@4.62.2)(typescript@6.0.3)(vue-i18n@11.4.6)(vue@3.5.38) + '@intlify/utils': 0.14.1 + '@miyaneee/rollup-plugin-json5': 1.2.0(rollup@4.62.2) + '@nuxt/kit': 4.4.8(magicast@0.5.3) + '@rollup/plugin-yaml': 4.1.2(rollup@4.62.2) + '@vue/compiler-sfc': 3.5.39 + defu: 6.1.7 + devalue: 5.8.1 + h3: 1.15.11 + knitwork: 1.3.0 + magic-string: 0.30.21 + mlly: 1.8.2 + nuxt-define: 1.0.0 + oxc-parser: 0.128.0 + oxc-transform: 0.128.0 + oxc-walker: 0.7.0(oxc-parser@0.128.0) + pathe: 2.0.3 + ufo: 1.6.4 + unplugin: 3.3.0(@voidzero-dev/vite-plus-core@0.1.19)(esbuild@0.28.1)(rolldown@1.1.4)(rollup@4.62.2) + unrouting: 0.1.7 + unstorage: 1.17.5(@netlify/blobs@10.7.9)(db0@0.3.4)(ioredis@5.11.1) + vue-i18n: 11.4.6(vue@3.5.38) + vue-router: 5.1.0(@voidzero-dev/vite-plus-core@0.1.19)(@vue/compiler-sfc@3.5.39)(esbuild@0.28.1)(rolldown@1.1.4)(rollup@4.62.2)(vue@3.5.38) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@farmfe/core' + - '@netlify/blobs' + - '@pinia/colada' + - '@planetscale/database' + - '@rspack/core' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - '@vue/compiler-dom' + - aws4fetch + - bun-types-no-globals + - db0 + - esbuild + - eslint + - idb-keyval + - ioredis + - magicast + - petite-vue-i18n + - pinia + - rolldown + - rollup + - supports-color + - typescript + - unloader + - uploadthing + - vite + - vue + - webpack + '@nuxtjs/mdc@0.22.1(magicast@0.5.3)': dependencies: '@nuxt/kit': 4.4.8(magicast@0.5.3) @@ -18223,6 +19089,9 @@ snapshots: '@oxc-parser/binding-android-arm-eabi@0.127.0': optional: true + '@oxc-parser/binding-android-arm-eabi@0.128.0': + optional: true + '@oxc-parser/binding-android-arm-eabi@0.129.0': optional: true @@ -18244,6 +19113,9 @@ snapshots: '@oxc-parser/binding-android-arm64@0.127.0': optional: true + '@oxc-parser/binding-android-arm64@0.128.0': + optional: true + '@oxc-parser/binding-android-arm64@0.129.0': optional: true @@ -18265,6 +19137,9 @@ snapshots: '@oxc-parser/binding-darwin-arm64@0.127.0': optional: true + '@oxc-parser/binding-darwin-arm64@0.128.0': + optional: true + '@oxc-parser/binding-darwin-arm64@0.129.0': optional: true @@ -18286,6 +19161,9 @@ snapshots: '@oxc-parser/binding-darwin-x64@0.127.0': optional: true + '@oxc-parser/binding-darwin-x64@0.128.0': + optional: true + '@oxc-parser/binding-darwin-x64@0.129.0': optional: true @@ -18307,6 +19185,9 @@ snapshots: '@oxc-parser/binding-freebsd-x64@0.127.0': optional: true + '@oxc-parser/binding-freebsd-x64@0.128.0': + optional: true + '@oxc-parser/binding-freebsd-x64@0.129.0': optional: true @@ -18328,6 +19209,9 @@ snapshots: '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.128.0': + optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.129.0': optional: true @@ -18349,6 +19233,9 @@ snapshots: '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.128.0': + optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.129.0': optional: true @@ -18370,6 +19257,9 @@ snapshots: '@oxc-parser/binding-linux-arm64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.128.0': + optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.129.0': optional: true @@ -18391,6 +19281,9 @@ snapshots: '@oxc-parser/binding-linux-arm64-musl@0.127.0': optional: true + '@oxc-parser/binding-linux-arm64-musl@0.128.0': + optional: true + '@oxc-parser/binding-linux-arm64-musl@0.129.0': optional: true @@ -18412,6 +19305,9 @@ snapshots: '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.128.0': + optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.129.0': optional: true @@ -18433,6 +19329,9 @@ snapshots: '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.128.0': + optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.129.0': optional: true @@ -18454,6 +19353,9 @@ snapshots: '@oxc-parser/binding-linux-riscv64-musl@0.127.0': optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.128.0': + optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.129.0': optional: true @@ -18475,6 +19377,9 @@ snapshots: '@oxc-parser/binding-linux-s390x-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.128.0': + optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.129.0': optional: true @@ -18496,6 +19401,9 @@ snapshots: '@oxc-parser/binding-linux-x64-gnu@0.127.0': optional: true + '@oxc-parser/binding-linux-x64-gnu@0.128.0': + optional: true + '@oxc-parser/binding-linux-x64-gnu@0.129.0': optional: true @@ -18517,6 +19425,9 @@ snapshots: '@oxc-parser/binding-linux-x64-musl@0.127.0': optional: true + '@oxc-parser/binding-linux-x64-musl@0.128.0': + optional: true + '@oxc-parser/binding-linux-x64-musl@0.129.0': optional: true @@ -18538,6 +19449,9 @@ snapshots: '@oxc-parser/binding-openharmony-arm64@0.127.0': optional: true + '@oxc-parser/binding-openharmony-arm64@0.128.0': + optional: true + '@oxc-parser/binding-openharmony-arm64@0.129.0': optional: true @@ -18563,6 +19477,13 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) optional: true + '@oxc-parser/binding-wasm32-wasi@0.128.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + '@oxc-parser/binding-wasm32-wasi@0.129.0': dependencies: '@emnapi/core': 1.10.0 @@ -18608,6 +19529,9 @@ snapshots: '@oxc-parser/binding-win32-arm64-msvc@0.127.0': optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.128.0': + optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.129.0': optional: true @@ -18629,6 +19553,9 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc@0.127.0': optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.128.0': + optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.129.0': optional: true @@ -18650,6 +19577,9 @@ snapshots: '@oxc-parser/binding-win32-x64-msvc@0.127.0': optional: true + '@oxc-parser/binding-win32-x64-msvc@0.128.0': + optional: true + '@oxc-parser/binding-win32-x64-msvc@0.129.0': optional: true @@ -18674,6 +19604,8 @@ snapshots: '@oxc-project/types@0.127.0': {} + '@oxc-project/types@0.128.0': {} + '@oxc-project/types@0.129.0': {} '@oxc-project/types@0.130.0': {} @@ -18747,54 +19679,109 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.23.0': optional: true + '@oxc-transform/binding-android-arm-eabi@0.128.0': + optional: true + '@oxc-transform/binding-android-arm-eabi@0.133.0': optional: true + '@oxc-transform/binding-android-arm64@0.128.0': + optional: true + '@oxc-transform/binding-android-arm64@0.133.0': optional: true + '@oxc-transform/binding-darwin-arm64@0.128.0': + optional: true + '@oxc-transform/binding-darwin-arm64@0.133.0': optional: true + '@oxc-transform/binding-darwin-x64@0.128.0': + optional: true + '@oxc-transform/binding-darwin-x64@0.133.0': optional: true + '@oxc-transform/binding-freebsd-x64@0.128.0': + optional: true + '@oxc-transform/binding-freebsd-x64@0.133.0': optional: true + '@oxc-transform/binding-linux-arm-gnueabihf@0.128.0': + optional: true + '@oxc-transform/binding-linux-arm-gnueabihf@0.133.0': optional: true + '@oxc-transform/binding-linux-arm-musleabihf@0.128.0': + optional: true + '@oxc-transform/binding-linux-arm-musleabihf@0.133.0': optional: true + '@oxc-transform/binding-linux-arm64-gnu@0.128.0': + optional: true + '@oxc-transform/binding-linux-arm64-gnu@0.133.0': optional: true + '@oxc-transform/binding-linux-arm64-musl@0.128.0': + optional: true + '@oxc-transform/binding-linux-arm64-musl@0.133.0': optional: true + '@oxc-transform/binding-linux-ppc64-gnu@0.128.0': + optional: true + '@oxc-transform/binding-linux-ppc64-gnu@0.133.0': optional: true + '@oxc-transform/binding-linux-riscv64-gnu@0.128.0': + optional: true + '@oxc-transform/binding-linux-riscv64-gnu@0.133.0': optional: true + '@oxc-transform/binding-linux-riscv64-musl@0.128.0': + optional: true + '@oxc-transform/binding-linux-riscv64-musl@0.133.0': optional: true + '@oxc-transform/binding-linux-s390x-gnu@0.128.0': + optional: true + '@oxc-transform/binding-linux-s390x-gnu@0.133.0': optional: true + '@oxc-transform/binding-linux-x64-gnu@0.128.0': + optional: true + '@oxc-transform/binding-linux-x64-gnu@0.133.0': optional: true + '@oxc-transform/binding-linux-x64-musl@0.128.0': + optional: true + '@oxc-transform/binding-linux-x64-musl@0.133.0': optional: true + '@oxc-transform/binding-openharmony-arm64@0.128.0': + optional: true + '@oxc-transform/binding-openharmony-arm64@0.133.0': optional: true + '@oxc-transform/binding-wasm32-wasi@0.128.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + '@oxc-transform/binding-wasm32-wasi@0.133.0': dependencies: '@emnapi/core': 1.10.0 @@ -18802,12 +19789,21 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true + '@oxc-transform/binding-win32-arm64-msvc@0.128.0': + optional: true + '@oxc-transform/binding-win32-arm64-msvc@0.133.0': optional: true + '@oxc-transform/binding-win32-ia32-msvc@0.128.0': + optional: true + '@oxc-transform/binding-win32-ia32-msvc@0.133.0': optional: true + '@oxc-transform/binding-win32-x64-msvc@0.128.0': + optional: true + '@oxc-transform/binding-win32-x64-msvc@0.133.0': optional: true @@ -19336,6 +20332,14 @@ snapshots: optionalDependencies: rollup: 4.62.2 + '@rollup/plugin-yaml@4.1.2(rollup@4.62.2)': + dependencies: + '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + js-yaml: 4.3.0 + tosource: 2.0.0-alpha.3 + optionalDependencies: + rollup: 4.62.2 + '@rollup/pluginutils@5.4.0(rollup@4.62.2)': dependencies: '@types/estree': 1.0.9 @@ -20447,12 +21451,32 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.62.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@6.0.3) + '@typescript-eslint/types': 8.62.1 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.64.0': + dependencies: + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/visitor-keys': 8.64.0 + '@typescript-eslint/tsconfig-utils@8.62.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.62.1(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + '@typescript-eslint/types@8.62.1': {} + '@typescript-eslint/types@8.64.0': {} + '@typescript-eslint/typescript-estree@8.62.1(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.62.1(typescript@5.9.3) @@ -20468,11 +21492,31 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.62.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.62.1(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@6.0.3) + '@typescript-eslint/types': 8.62.1 + '@typescript-eslint/visitor-keys': 8.62.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@8.62.1': dependencies: '@typescript-eslint/types': 8.62.1 eslint-visitor-keys: 5.0.1 + '@typescript-eslint/visitor-keys@8.64.0': + dependencies: + '@typescript-eslint/types': 8.64.0 + eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.2': {} '@unhead/bundler@3.1.7(@voidzero-dev/vite-plus-core@0.1.19)(esbuild@0.28.1)(lightningcss@1.32.0)(rolldown@1.1.4)(rollup@4.62.2)(typescript@6.0.3)(unhead@2.1.15)': @@ -21870,6 +22914,8 @@ snapshots: commander@2.20.3: {} + commander@6.2.1: {} + comment-parser@1.4.7: {} commitizen@4.3.2(@types/node@24.12.4)(typescript@6.0.3): @@ -22404,6 +23450,11 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + dot-object@2.1.5: + dependencies: + commander: 6.2.1 + glob: 7.2.3 + dot-prop@10.1.0: dependencies: type-fest: 5.8.0 @@ -22671,6 +23722,35 @@ snapshots: es6-error@4.1.1: {} + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + esbuild@0.27.7: optionalDependencies: '@esbuild/aix-ppc64': 0.27.7 @@ -22847,6 +23927,12 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.17.0) eslint-visitor-keys: 4.2.1 + espree@9.6.1: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 3.4.3 + esprima@4.0.1: {} esquery@1.7.0: @@ -23381,6 +24467,14 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.9 + once: 1.4.0 + global-agent@3.0.0: dependencies: boolean: 3.2.0 @@ -24098,6 +25192,8 @@ snapshots: is-utf8@0.2.1: {} + is-valid-glob@1.0.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -24172,6 +25268,8 @@ snapshots: filelist: 1.0.6 picocolors: 1.1.1 + jiti@1.21.7: {} + jiti@2.6.1: {} jiti@2.7.0: {} @@ -24272,6 +25370,13 @@ snapshots: json5@2.2.3: {} + jsonc-eslint-parser@2.4.2: + dependencies: + acorn: 8.17.0 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.8.5 + jsonc-parser@3.3.1: {} jsonfile@6.2.1: @@ -25794,6 +26899,8 @@ snapshots: transitivePeerDependencies: - magicast + nuxt-define@1.0.0: {} + nuxt-link-checker@5.1.2(@netlify/blobs@10.7.9)(@nuxt/schema@4.4.8)(@voidzero-dev/vite-plus-core@0.1.19)(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@4.4.8)(vue@3.5.38)(zod@4.4.3): dependencies: '@nuxt/kit': 4.4.8(magicast@0.5.3) @@ -26633,6 +27740,31 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + oxc-parser@0.128.0: + dependencies: + '@oxc-project/types': 0.128.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.128.0 + '@oxc-parser/binding-android-arm64': 0.128.0 + '@oxc-parser/binding-darwin-arm64': 0.128.0 + '@oxc-parser/binding-darwin-x64': 0.128.0 + '@oxc-parser/binding-freebsd-x64': 0.128.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.128.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.128.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.128.0 + '@oxc-parser/binding-linux-arm64-musl': 0.128.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.128.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.128.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.128.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.128.0 + '@oxc-parser/binding-linux-x64-gnu': 0.128.0 + '@oxc-parser/binding-linux-x64-musl': 0.128.0 + '@oxc-parser/binding-openharmony-arm64': 0.128.0 + '@oxc-parser/binding-wasm32-wasi': 0.128.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.128.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.128.0 + '@oxc-parser/binding-win32-x64-msvc': 0.128.0 + oxc-parser@0.129.0: dependencies: '@oxc-project/types': 0.129.0 @@ -26805,6 +27937,29 @@ snapshots: '@oxc-resolver/binding-win32-arm64-msvc': 11.23.0 '@oxc-resolver/binding-win32-x64-msvc': 11.23.0 + oxc-transform@0.128.0: + optionalDependencies: + '@oxc-transform/binding-android-arm-eabi': 0.128.0 + '@oxc-transform/binding-android-arm64': 0.128.0 + '@oxc-transform/binding-darwin-arm64': 0.128.0 + '@oxc-transform/binding-darwin-x64': 0.128.0 + '@oxc-transform/binding-freebsd-x64': 0.128.0 + '@oxc-transform/binding-linux-arm-gnueabihf': 0.128.0 + '@oxc-transform/binding-linux-arm-musleabihf': 0.128.0 + '@oxc-transform/binding-linux-arm64-gnu': 0.128.0 + '@oxc-transform/binding-linux-arm64-musl': 0.128.0 + '@oxc-transform/binding-linux-ppc64-gnu': 0.128.0 + '@oxc-transform/binding-linux-riscv64-gnu': 0.128.0 + '@oxc-transform/binding-linux-riscv64-musl': 0.128.0 + '@oxc-transform/binding-linux-s390x-gnu': 0.128.0 + '@oxc-transform/binding-linux-x64-gnu': 0.128.0 + '@oxc-transform/binding-linux-x64-musl': 0.128.0 + '@oxc-transform/binding-openharmony-arm64': 0.128.0 + '@oxc-transform/binding-wasm32-wasi': 0.128.0 + '@oxc-transform/binding-win32-arm64-msvc': 0.128.0 + '@oxc-transform/binding-win32-ia32-msvc': 0.128.0 + '@oxc-transform/binding-win32-x64-msvc': 0.128.0 + oxc-transform@0.133.0: optionalDependencies: '@oxc-transform/binding-android-arm-eabi': 0.133.0 @@ -26828,6 +27983,11 @@ snapshots: '@oxc-transform/binding-win32-ia32-msvc': 0.133.0 '@oxc-transform/binding-win32-x64-msvc': 0.133.0 + oxc-walker@0.7.0(oxc-parser@0.128.0): + dependencies: + magic-regexp: 0.10.0 + oxc-parser: 0.128.0 + oxc-walker@1.0.0(@voidzero-dev/vite-plus-core@0.1.19)(esbuild@0.28.1)(oxc-parser@0.132.0)(rolldown@1.1.4)(rollup@4.62.2): dependencies: magic-regexp: 0.11.0(@voidzero-dev/vite-plus-core@0.1.19)(esbuild@0.28.1)(rolldown@1.1.4)(rollup@4.62.2) @@ -29080,6 +30240,8 @@ snapshots: tomlify-j0.4@3.0.0: {} + tosource@2.0.0-alpha.3: {} + totalist@3.0.1: {} tough-cookie@6.0.1: @@ -29118,6 +30280,10 @@ snapshots: dependencies: typescript: 5.9.3 + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + ts-dedent@2.3.0: {} ts-map@1.0.3: {} @@ -29201,6 +30367,8 @@ snapshots: ultrahtml@1.6.0: {} + ultramatter@0.0.4: {} + unagent@0.0.8(@huggingface/transformers@4.2.0)(pg@8.22.0)(sqlite-vec@0.1.9)(unstorage@1.17.5): dependencies: croner: 9.1.0 @@ -29872,6 +31040,22 @@ snapshots: vue: 3.5.38(typescript@6.0.3) vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.38) + vue-i18n-extract@2.0.7: + dependencies: + cac: 6.7.14 + dot-object: 2.1.5 + glob: 8.1.0 + is-valid-glob: 1.0.0 + js-yaml: 4.3.0 + + vue-i18n@11.4.6(vue@3.5.38): + dependencies: + '@intlify/core-base': 11.4.6 + '@intlify/devtools-types': 11.4.6 + '@intlify/shared': 11.4.6 + '@vue/devtools-api': 6.6.4 + vue: 3.5.38(typescript@6.0.3) + vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.5.38): dependencies: vue: 3.5.38(typescript@6.0.3) @@ -30258,6 +31442,11 @@ snapshots: yallist@5.0.0: {} + yaml-eslint-parser@1.3.2: + dependencies: + eslint-visitor-keys: 3.4.3 + yaml: 2.9.0 + yaml@2.9.0: {} yargs-parser@21.1.1: {} diff --git a/scripts/compare-translations.ts b/scripts/compare-translations.ts new file mode 100644 index 000000000..fec9044e4 --- /dev/null +++ b/scripts/compare-translations.ts @@ -0,0 +1,424 @@ +/* oxlint-disable no-console */ +import type { LocaleObject } from "@nuxtjs/i18n"; +import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { basename, join, resolve } from "node:path"; +import * as process from "node:process"; +import { deepCopy } from "@intlify/shared"; +import { countryLocaleVariants, currentLocales } from "../config/i18n.ts"; +import { COLORS } from "./utils/colors.ts"; + +const LOCALES_DIRECTORY = join(import.meta.dirname, "../i18n/locales"); +const REFERENCE_FILE_NAME = "en.json"; + +type NestedObject = { [key: string]: unknown }; +interface LocaleInfo { + filePath: string; + locale: string; + lang: string; + country?: string; + forCountry?: boolean; + mergeLocale?: boolean; +} + +const countries = new Map>(); +const availableLocales = new Map(); + +function extractLocalInfo(filePath: string): LocaleInfo { + const locale = basename(filePath, ".json"); + const [lang, country] = locale.split("-"); + return { filePath, locale, lang: lang ?? locale, country }; +} + +function createVariantInfo( + code: string, + options: { forCountry: boolean; mergeLocale: boolean }, +): LocaleInfo { + const [lang, country] = code.split("-"); + return { filePath: "", locale: code, lang: lang ?? code, country, ...options }; +} + +const populateLocaleCountries = (): void => { + for (const lang of Object.keys(countryLocaleVariants)) { + const variants = countryLocaleVariants[lang]; + if (!variants) continue; + for (const variant of variants) { + if (!countries.has(lang)) { + countries.set(lang, new Map()); + } + if (variant.country) { + countries + .get(lang)! + .set(lang, createVariantInfo(lang, { forCountry: true, mergeLocale: false })); + countries + .get(lang)! + .set( + variant.code, + createVariantInfo(variant.code, { forCountry: true, mergeLocale: true }), + ); + } else { + countries + .get(lang)! + .set( + variant.code, + createVariantInfo(variant.code, { forCountry: false, mergeLocale: true }), + ); + } + } + } + + for (const localeData of currentLocales) { + availableLocales.set(localeData.code, localeData); + } +}; + +const checkCountryVariant = (localeInfo: LocaleInfo): void => { + const { locale, lang, country } = localeInfo; + const countryVariant = countries.get(lang); + if (countryVariant) { + if (country) { + const found = countryVariant.get(locale); + if (!found) { + console.error( + `${COLORS.red}Error: Invalid locale file "${locale}", it should be included at "countryLocaleVariants" in config/i18n.ts"${COLORS.reset}`, + ); + process.exit(1); + } + localeInfo.forCountry = found.forCountry; + localeInfo.mergeLocale = found.mergeLocale; + } else { + localeInfo.forCountry = false; + localeInfo.mergeLocale = false; + } + } else if (!country && lang !== "en") { + // Standalone language codes without a country are only valid when registered + // as a base in countryLocaleVariants (e.g. future `es` / `de` bases). + console.error( + `${COLORS.red}Error: Invalid locale file "${locale}", it should be included at "countryLocaleVariants" in config/i18n.ts, or change the name to include country name "${lang}-"${COLORS.reset}`, + ); + process.exit(1); + } +}; + +const checkJsonName = (filePath: string): LocaleInfo => { + const info = extractLocalInfo(filePath); + checkCountryVariant(info); + return info; +}; + +function getFileName(file: string | { path: string }): string { + return typeof file === "string" ? file : file.path; +} + +const loadJson = async ({ filePath, mergeLocale, locale }: LocaleInfo): Promise => { + if (!existsSync(filePath)) { + console.error(`${COLORS.red}Error: File not found at ${filePath}${COLORS.reset}`); + process.exit(1); + } + + if (!mergeLocale) { + return JSON.parse(readFileSync(filePath, "utf-8")) as NestedObject; + } + + const localeObject = availableLocales.get(locale); + if (!localeObject) { + console.error( + `${COLORS.red}Error: Locale "${locale}" not found in currentLocales${COLORS.reset}`, + ); + process.exit(1); + } + + const localesFolder = resolve("i18n/locales"); + const files = localeObject.files ?? []; + if (localeObject.file || files.length === 1) { + const fileName = + (localeObject.file ? getFileName(localeObject.file) : undefined) ?? + (files[0] ? getFileName(files[0]) : undefined); + if (!fileName) return {}; + return JSON.parse(readFileSync(join(localesFolder, fileName), "utf-8")) as NestedObject; + } + + const firstFile = files[0]; + if (!firstFile) return {}; + const source = JSON.parse( + readFileSync(join(localesFolder, getFileName(firstFile)), "utf-8"), + ) as NestedObject; + for (let i = 1; i < files.length; i++) { + const file = files[i]; + if (!file) continue; + const overlay = JSON.parse(readFileSync(join(localesFolder, getFileName(file)), "utf-8")); + deepCopy(overlay, source); + } + return source; +}; + +type SyncStats = { + missing: string[]; + extra: string[]; + referenceKeys: string[]; +}; + +const isNested = (val: unknown): val is NestedObject => + val !== null && typeof val === "object" && !Array.isArray(val); + +const syncLocaleData = ( + reference: NestedObject, + target: NestedObject, + stats: SyncStats, + fix: boolean, + prefix = "", +): NestedObject => { + const result: NestedObject = {}; + + for (const key of Object.keys(reference)) { + const propertyPath = prefix ? `${prefix}.${key}` : key; + const refValue = reference[key]; + + if (isNested(refValue)) { + const nextTarget = isNested(target[key]) ? target[key] : {}; + const data = syncLocaleData(refValue, nextTarget, stats, fix, propertyPath); + if (fix && Object.keys(data).length === 0) { + delete result[key]; + } else { + result[key] = data; + } + } else { + stats.referenceKeys.push(propertyPath); + + if (key in target) { + result[key] = target[key]; + } else { + stats.missing.push(propertyPath); + if (fix) { + result[key] = `EN TEXT TO REPLACE: ${refValue}`; + } + } + } + } + + for (const key of Object.keys(target)) { + const propertyPath = prefix ? `${prefix}.${key}` : key; + if (!(key in reference)) { + stats.extra.push(propertyPath); + } + } + + return result; +}; + +const logSection = ( + title: string, + keys: string[], + color: string, + _icon: string, + emptyMessage: string, +): void => { + console.log(`\n${color}${title}${COLORS.reset}`); + if (keys.length === 0) { + console.log(` ${COLORS.green}${emptyMessage}${COLORS.reset}`); + return; + } + for (const key of keys) { + console.log(` - ${key}`); + } +}; + +const processLocale = async ( + singleLocale: boolean, + localeFile: string, + referenceContent: NestedObject, + fix = false, +): Promise => { + const filePath = join(LOCALES_DIRECTORY, localeFile); + const localeInfo = checkJsonName(filePath); + + if (fix && localeInfo.mergeLocale && singleLocale) { + console.error( + `${COLORS.red}Error: Locale "${localeInfo.locale}" cannot be fixed, fix the ${localeInfo.lang} locale instead!${COLORS.reset}`, + ); + process.exit(1); + } + + const targetContent = await loadJson(localeInfo); + const { $schema: targetSchema, ...targetWithoutSchema } = targetContent; + + const stats: SyncStats = { + missing: [], + extra: [], + referenceKeys: [], + }; + + const newContent = syncLocaleData(referenceContent, targetWithoutSchema, stats, fix); + + if (!localeInfo.mergeLocale && (stats.extra.length > 0 || fix)) { + const output = targetSchema ? { $schema: targetSchema, ...newContent } : newContent; + writeFileSync(filePath, `${JSON.stringify(output, null, 2)}\n`, "utf-8"); + } + + return stats; +}; + +const runSingleLocale = async ( + locale: string, + referenceContent: NestedObject, + fix = false, +): Promise => { + const localeFile = locale.endsWith(".json") ? locale : `${locale}.json`; + const filePath = join(LOCALES_DIRECTORY, localeFile); + + if (!existsSync(filePath)) { + console.error(`${COLORS.red}Error: Locale file not found: ${localeFile}${COLORS.reset}`); + process.exit(1); + } + + const { missing, extra, referenceKeys } = await processLocale( + true, + localeFile, + referenceContent, + fix, + ); + + console.log( + `${COLORS.cyan}=== Missing keys for ${localeFile}${fix ? " (with --fix)" : ""} ===${COLORS.reset}`, + ); + console.log(`Reference: ${REFERENCE_FILE_NAME} (${referenceKeys.length} keys)`); + + if (missing.length > 0) { + if (fix) { + console.log( + `\n${COLORS.green}Added ${missing.length} missing key(s) with EN placeholder:${COLORS.reset}`, + ); + for (const key of missing) { + console.log(` - ${key}`); + } + } else { + console.log(`\n${COLORS.yellow}Missing ${missing.length} key(s):${COLORS.reset}`); + for (const key of missing) { + console.log(` - ${key}`); + } + } + } else { + console.log(`\n${COLORS.green}No missing keys!${COLORS.reset}`); + } + + if (extra.length > 0) { + console.log(`\n${COLORS.magenta}Removed ${extra.length} extra key(s):${COLORS.reset}`); + for (const key of extra) { + console.log(` - ${key}`); + } + } + console.log(""); +}; + +const runAllLocales = async (referenceContent: NestedObject, fix = false): Promise => { + const localeFiles = readdirSync(LOCALES_DIRECTORY).filter( + (file) => file.endsWith(".json") && file !== REFERENCE_FILE_NAME, + ); + + const results: (SyncStats & { file: string })[] = []; + + let totalMissing = 0; + let totalRemoved = 0; + let totalAdded = 0; + + for (const localeFile of localeFiles) { + const stats = await processLocale(false, localeFile, referenceContent, fix); + results.push({ + file: localeFile, + ...stats, + }); + + if (fix) { + if (stats.missing.length > 0) totalAdded += stats.missing.length; + } else if (stats.missing.length > 0) { + totalMissing += stats.missing.length; + } + if (stats.extra.length > 0) totalRemoved += stats.extra.length; + } + + const referenceKeysCount = results.length > 0 ? results[0]!.referenceKeys.length : 0; + + console.log( + `${COLORS.cyan}=== Translation Audit${fix ? " (with --fix)" : ""} ===${COLORS.reset}`, + ); + console.log(`Reference: ${REFERENCE_FILE_NAME} (${referenceKeysCount} keys)`); + console.log(`Checking ${localeFiles.length} locale(s)...`); + + for (const res of results) { + if (res.missing.length > 0 || res.extra.length > 0) { + console.log(`\n${COLORS.cyan}--- ${res.file} ---${COLORS.reset}`); + + if (res.missing.length > 0) { + if (fix) { + logSection( + "ADDED MISSING KEYS (with EN placeholder)", + res.missing, + COLORS.green, + "", + "", + ); + } else { + logSection( + "MISSING KEYS (in en.json but not in this locale)", + res.missing, + COLORS.yellow, + "", + "", + ); + } + } + + if (res.extra.length > 0) { + logSection( + "REMOVED EXTRA KEYS (were in this locale but not in en.json)", + res.extra, + COLORS.magenta, + "", + "", + ); + } + } + } + + console.log(`\n${COLORS.cyan}=== Summary ===${COLORS.reset}`); + if (totalAdded > 0) { + console.log( + `${COLORS.green} Added missing keys (EN placeholder): ${totalAdded}${COLORS.reset}`, + ); + } + if (totalMissing > 0) { + console.log( + `${COLORS.yellow} Missing keys across all locales: ${totalMissing}${COLORS.reset}`, + ); + } + if (totalRemoved > 0) { + console.log(`${COLORS.magenta} Removed extra keys: ${totalRemoved}${COLORS.reset}`); + } + if (totalMissing === 0 && totalRemoved === 0 && totalAdded === 0) { + console.log(`${COLORS.green} All locales are in sync!${COLORS.reset}`); + } + console.log(""); +}; + +const run = async (): Promise => { + populateLocaleCountries(); + const referenceFilePath = join(LOCALES_DIRECTORY, REFERENCE_FILE_NAME); + const referenceContent = await loadJson({ + filePath: referenceFilePath, + locale: "en", + lang: "en", + }); + + delete referenceContent.$schema; + + const args = process.argv.slice(2); + const fix = args.includes("--fix"); + const targetLocale = args.find((arg) => !arg.startsWith("--")); + + if (targetLocale) { + await runSingleLocale(targetLocale, referenceContent, fix); + } else { + await runAllLocales(referenceContent, fix); + } +}; + +await run(); diff --git a/scripts/find-invalid-translations.ts b/scripts/find-invalid-translations.ts new file mode 100644 index 000000000..3d3920db5 --- /dev/null +++ b/scripts/find-invalid-translations.ts @@ -0,0 +1,84 @@ +import { join } from "node:path"; +/* oxlint-disable no-console */ +import process from "node:process"; +import { createI18NReport, type I18NItem } from "vue-i18n-extract"; +import { colors } from "./utils/colors.ts"; + +const LOCALES_DIRECTORY = join(import.meta.dirname, "../i18n/locales"); +const REFERENCE_FILE_NAME = "en.json"; +const VUE_FILES_GLOB = "./app/**/*.?(vue|ts|js)"; + +function printSection( + title: string, + items: I18NItem[], + status: "error" | "warning" | "success", +): void { + const icon = status === "error" ? "❌" : status === "warning" ? "⚠️" : "✅"; + const colorFn = + status === "error" ? colors.red : status === "warning" ? colors.yellow : colors.green; + + console.log(`\n${icon} ${colors.bold(title)}: ${colorFn(String(items.length))}`); + + if (items.length === 0) return; + + const groupedByFile = items.reduce>((acc, item) => { + const file = item.file ?? "unknown"; + acc[file] ??= []; + acc[file]!.push(item.path); + return acc; + }, {}); + + for (const [file, keys] of Object.entries(groupedByFile)) { + console.log(` ${colors.dim(file)}`); + for (const key of keys) { + console.log(` ${colors.cyan(key)}`); + } + } +} + +async function run(): Promise { + console.log(colors.bold("\n🔍 Analyzing i18n translations...\n")); + + const { missingKeys, unusedKeys, maybeDynamicKeys } = await createI18NReport({ + vueFiles: VUE_FILES_GLOB, + languageFiles: join(LOCALES_DIRECTORY, REFERENCE_FILE_NAME), + exclude: ["$schema"], + }); + + const hasMissingKeys = missingKeys.length > 0; + const hasUnusedKeys = unusedKeys.length > 0; + const hasDynamicKeys = maybeDynamicKeys.length > 0; + + printSection("Missing keys", missingKeys, hasMissingKeys ? "error" : "success"); + + printSection("Unused keys", unusedKeys, hasUnusedKeys ? "error" : "success"); + + printSection( + "Dynamic keys (cannot be statically analyzed)", + maybeDynamicKeys, + hasDynamicKeys ? "error" : "success", + ); + + console.log(`\n${colors.dim("─".repeat(50))}`); + + const shouldFail = hasMissingKeys || hasDynamicKeys || hasUnusedKeys; + + if (shouldFail) { + console.log(colors.red("\n❌ Build failed: missing, unused or dynamic keys detected")); + console.log(colors.dim(" Fix missing keys by adding them to the locale file")); + console.log(colors.dim(" Fix dynamic keys by using static translation keys\n")); + console.log( + colors.dim( + " Fix unused keys by removing them from the locale file (pnpm run i18n:report:fix)\n", + ), + ); + process.exit(1); + } else { + console.log(colors.green("\n✅ All translations are valid!\n")); + } +} + +run().catch((error: unknown) => { + console.error(colors.red("\n❌ Unexpected error:"), error); + process.exit(1); +}); diff --git a/scripts/generate-i18n-schema.ts b/scripts/generate-i18n-schema.ts new file mode 100644 index 000000000..dbbcfcc73 --- /dev/null +++ b/scripts/generate-i18n-schema.ts @@ -0,0 +1,73 @@ +/* oxlint-disable no-console */ +import { readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { colors } from "./utils/colors.ts"; + +const I18N_DIRECTORY = join(import.meta.dirname, "../i18n"); +const LOCALES_DIRECTORY = join(I18N_DIRECTORY, "locales"); +const REFERENCE_FILE_NAME = "en.json"; +const SCHEMA_FILE_NAME = "schema.json"; + +type Json = Record; +type LocaleJson = Json & { $schema: string }; + +interface JsonSchema { + $schema?: string; + title?: string; + description?: string; + type: string; + properties?: Record; + additionalProperties?: boolean; +} + +const generateSubSchema = (obj: Json): JsonSchema => { + const properties: Record = {}; + + for (const [key, value] of Object.entries(obj)) { + if (value !== null && typeof value === "object" && !Array.isArray(value)) { + properties[key] = generateSubSchema(value as Json); + } else { + properties[key] = { type: "string" }; + } + } + + return { + type: "object", + properties, + additionalProperties: false, + }; +}; + +const generateSchema = (obj: LocaleJson): JsonSchema => { + const { $schema: _, ...rest } = obj; + const baseSchema = generateSubSchema(rest); + return { + $schema: "http://json-schema.org/draft-07/schema#", + title: "WolfStar i18n locale file", + description: + "Schema for WolfStar i18n translation files. Generated from en.json — do not edit manually.", + ...baseSchema, + properties: { + ...baseSchema.properties, + $schema: { type: "string" }, + }, + }; +}; + +/* + * Generates a JSON Schema from the reference locale file (en.json) and writes it to + * i18n/schema.json. All locale files include a $schema property that points to this file. + */ +const main = async (): Promise => { + const referenceFilePath = join(LOCALES_DIRECTORY, REFERENCE_FILE_NAME); + const referenceContent = JSON.parse(await readFile(referenceFilePath, "utf-8")) as LocaleJson; + + const schema = generateSchema(referenceContent); + + const schemaFilePath = join(I18N_DIRECTORY, SCHEMA_FILE_NAME); + await writeFile(schemaFilePath, `${JSON.stringify(schema, null, 2)}\n`, "utf-8"); + + console.log(colors.green(`✅ Generated schema at ${schemaFilePath}`)); +}; + +await main(); diff --git a/scripts/remove-unused-translations.ts b/scripts/remove-unused-translations.ts new file mode 100644 index 000000000..f3e69059d --- /dev/null +++ b/scripts/remove-unused-translations.ts @@ -0,0 +1,104 @@ +import { readdir, readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +/* oxlint-disable no-console */ +import process from "node:process"; +import { createI18NReport, type I18NItem } from "vue-i18n-extract"; +import { colors } from "./utils/colors.ts"; + +const LOCALES_DIRECTORY = join(import.meta.dirname, "../i18n/locales"); +const REFERENCE_FILE_NAME = "en.json"; +const VUE_FILES_GLOB = "./app/**/*.?(vue|ts|js)"; + +type NestedObject = Record; + +/** Removes a key path (e.g. "foo.bar.baz") from a nested object. Cleans up empty parents. */ +function removeKey(obj: NestedObject, path: string): boolean { + const parts = path.split("."); + if (parts.length === 1) { + if (path in obj) { + delete obj[path]; + return true; + } + return false; + } + const [first, ...rest] = parts; + if (!first) return false; + const child = obj[first]; + if (child && typeof child === "object" && !Array.isArray(child)) { + const removed = removeKey(child as NestedObject, rest.join(".")); + if (removed && Object.keys(child as object).length === 0) { + delete obj[first]; + } + return removed; + } + return false; +} + +/** Removes multiple keys from a nested object. Sorts by depth (deepest first) to avoid parent/child conflicts. */ +function removeKeysFromObject(obj: NestedObject, keys: string[]): number { + const sortedKeys = [...keys].toSorted((a, b) => b.split(".").length - a.split(".").length); + let removed = 0; + for (const key of sortedKeys) { + if (removeKey(obj, key)) removed++; + } + return removed; +} + +async function run(): Promise { + console.log(colors.bold("\n🔍 Removing unused i18n translations...\n")); + + const referenceFilePath = join(LOCALES_DIRECTORY, REFERENCE_FILE_NAME); + + const { unusedKeys } = await createI18NReport({ + vueFiles: VUE_FILES_GLOB, + languageFiles: referenceFilePath, + exclude: ["$schema"], + }); + + if (unusedKeys.length === 0) { + console.log(colors.green("✅ No unused translations found. Nothing to remove.\n")); + return; + } + + const uniquePaths = [...new Set(unusedKeys.map((item: I18NItem) => item.path))]; + + const referenceContent = JSON.parse(await readFile(referenceFilePath, "utf-8")) as NestedObject; + const refRemoved = removeKeysFromObject(referenceContent, uniquePaths); + await writeFile(referenceFilePath, `${JSON.stringify(referenceContent, null, 2)}\n`, "utf-8"); + + const localeFiles = (await readdir(LOCALES_DIRECTORY)).filter( + (f) => f.endsWith(".json") && f !== REFERENCE_FILE_NAME, + ); + + const otherLocalesSummary: { file: string; removed: number }[] = []; + let totalOtherRemoved = 0; + + for (const localeFile of localeFiles) { + const filePath = join(LOCALES_DIRECTORY, localeFile); + const content = JSON.parse(await readFile(filePath, "utf-8")) as NestedObject; + const removed = removeKeysFromObject(content, uniquePaths); + if (removed > 0) { + await writeFile(filePath, `${JSON.stringify(content, null, 2)}\n`, "utf-8"); + otherLocalesSummary.push({ file: localeFile, removed }); + totalOtherRemoved += removed; + } + } + + console.log(colors.green(`✅ Removed ${refRemoved} keys from ${REFERENCE_FILE_NAME}`)); + if (otherLocalesSummary.length > 0) { + console.log( + colors.green( + `✅ Removed ${totalOtherRemoved} keys from ${otherLocalesSummary.length} other locale(s)`, + ), + ); + for (const { file, removed } of otherLocalesSummary) { + console.log(colors.dim(` ${file}: ${removed} keys`)); + } + } + console.log(colors.dim(`\nTotal: ${uniquePaths.length} unique unused key(s) cleaned up\n`)); +} + +run().catch((error: unknown) => { + console.error(colors.red("\n❌ Unexpected error:"), error); + process.exit(1); +}); diff --git a/scripts/utils/colors.ts b/scripts/utils/colors.ts new file mode 100644 index 000000000..31976f781 --- /dev/null +++ b/scripts/utils/colors.ts @@ -0,0 +1,17 @@ +export const COLORS = { + reset: "\x1B[0m", + red: "\x1B[31m", + green: "\x1B[32m", + yellow: "\x1B[33m", + magenta: "\x1B[35m", + cyan: "\x1B[36m", +} as const; + +export const colors = { + red: (text: string) => `\x1B[31m${text}\x1B[0m`, + green: (text: string) => `\x1B[32m${text}\x1B[0m`, + yellow: (text: string) => `\x1B[33m${text}\x1B[0m`, + cyan: (text: string) => `\x1B[36m${text}\x1B[0m`, + dim: (text: string) => `\x1B[2m${text}\x1B[0m`, + bold: (text: string) => `\x1B[1m${text}\x1B[0m`, +}; diff --git a/shared/types/i18n-status.ts b/shared/types/i18n-status.ts new file mode 100644 index 000000000..d040fa5cd --- /dev/null +++ b/shared/types/i18n-status.ts @@ -0,0 +1,40 @@ +import type { Directions, LocaleObject } from "@nuxtjs/i18n"; + +type Locale = LocaleObject["code"]; + +/** + * Translation status data generated by the Lunaria build. + */ +export interface I18nStatus { + /** ISO timestamp when this status was generated */ + generatedAt: string; + /** The source locale (English) */ + sourceLocale: { + lang: Locale; + label: string; + totalKeys: number; + }; + /** Status for each target locale */ + locales: I18nLocaleStatus[]; +} + +export interface I18nLocaleStatus { + /** Locale code (e.g. 'es-ES') */ + lang: Locale; + /** Display name (e.g. 'Español') */ + label: string; + /** locale direction - rtl | ltr */ + dir: Directions; + /** Total number of translation keys */ + totalKeys: number; + /** Number of completed translations */ + completedKeys: number; + /** List of missing key paths (dot notation) */ + missingKeys: string[]; + /** Completion percentage (0-100) */ + percentComplete: number; + /** GitHub URL to edit this locale's translation file */ + githubEditUrl: string; + /** GitHub URL to view history of this locale's translation file */ + githubHistoryUrl: string; +} diff --git a/test/nuxt/composables/useFooter.spec.ts b/test/nuxt/composables/useFooter.spec.ts index 3989a671a..ce7d68b83 100644 --- a/test/nuxt/composables/useFooter.spec.ts +++ b/test/nuxt/composables/useFooter.spec.ts @@ -1,62 +1,78 @@ +import { mountSuspended } from "@nuxt/test-utils/runtime"; import { describe, expect, it } from "vitest"; +import { defineComponent } from "vue"; describe("useFooter", () => { - it("should return an object with a columns computed property", () => { - const { columns } = useFooter(); + async function setup() { + let composable: ReturnType | undefined; + + const Child = defineComponent({ + setup() { + composable = useFooter(); + return () => null; + }, + }); + + await mountSuspended(Child); + return composable!; + } + + it("should return an object with a columns computed property", async () => { + const { columns } = await setup(); expect(columns).toBeDefined(); expect(columns.value).toBeInstanceOf(Array); }); - it("should have three columns", () => { - const { columns } = useFooter(); + it("should have three columns", async () => { + const { columns } = await setup(); expect(columns.value).toHaveLength(3); }); - it("should have Product as the first column", () => { - const { columns } = useFooter(); + it("should have Product as the first column", async () => { + const { columns } = await setup(); expect(columns.value[0]!.label).toBe("Product"); }); - it("should have Community as the second column", () => { - const { columns } = useFooter(); + it("should have Community as the second column", async () => { + const { columns } = await setup(); expect(columns.value[1]!.label).toBe("Community"); }); - it("should have Legal as the third column", () => { - const { columns } = useFooter(); + it("should have Legal as the third column", async () => { + const { columns } = await setup(); expect(columns.value[2]!.label).toBe("Legal"); }); - it("should have 3 children in the Product column", () => { - const { columns } = useFooter(); + it("should have 3 children in the Product column", async () => { + const { columns } = await setup(); expect(columns.value[0]!.children).toHaveLength(3); }); - it("should have 3 children in the Community column", () => { - const { columns } = useFooter(); - expect(columns.value[1]!.children).toHaveLength(3); + it("should have 4 children in the Community column", async () => { + const { columns } = await setup(); + expect(columns.value[1]!.children).toHaveLength(4); }); - it("should have 2 children in the Legal column", () => { - const { columns } = useFooter(); + it("should have 2 children in the Legal column", async () => { + const { columns } = await setup(); expect(columns.value[2]!.children).toHaveLength(2); }); - it("should include Support Server in Community", () => { - const { columns } = useFooter(); - const labels = columns.value[1]!.children!.map((c: any) => c.label); + it("should include Support Server in Community", async () => { + const { columns } = await setup(); + const labels = columns.value[1]!.children!.map((c) => c.label); expect(labels).toContain("Support Server"); }); - it("should include Terms of Use in Legal", () => { - const { columns } = useFooter(); - const labels = columns.value[2]!.children!.map((c: any) => c.label); + it("should include Terms of Use in Legal", async () => { + const { columns } = await setup(); + const labels = columns.value[2]!.children!.map((c) => c.label); expect(labels).toContain("Terms of Use"); }); - it("should include Privacy Policy in Legal", () => { - const { columns } = useFooter(); - const labels = columns.value[2]!.children!.map((c: any) => c.label); + it("should include Privacy Policy in Legal", async () => { + const { columns } = await setup(); + const labels = columns.value[2]!.children!.map((c) => c.label); expect(labels).toContain("Privacy Policy"); }); }); diff --git a/test/unit/i18n/locale-files.test.ts b/test/unit/i18n/locale-files.test.ts new file mode 100644 index 000000000..2c8488d36 --- /dev/null +++ b/test/unit/i18n/locale-files.test.ts @@ -0,0 +1,55 @@ +import { existsSync, readFileSync, readdirSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { currentLocales } from "../../../config/i18n"; + +const LOCALES_DIR = join(process.cwd(), "i18n/locales"); +const REFERENCE = "en.json"; + +type Nested = Record; + +function collectKeys(obj: Nested, prefix = ""): string[] { + const keys: string[] = []; + for (const [key, value] of Object.entries(obj)) { + if (key === "$schema") continue; + const path = prefix ? `${prefix}.${key}` : key; + if (typeof value === "object" && value !== null && !Array.isArray(value)) { + keys.push(...collectKeys(value as Nested, path)); + } else { + keys.push(path); + } + } + return keys; +} + +describe("i18n locale files", () => { + it("has a reference en.json", () => { + expect(existsSync(join(LOCALES_DIR, REFERENCE))).toBe(true); + }); + + it("registers a locale file for every currentLocales entry", () => { + for (const locale of currentLocales) { + const file = locale.file ?? `${locale.code}.json`; + const fileName = typeof file === "string" ? file : file.path; + expect(existsSync(join(LOCALES_DIR, fileName)), `missing ${fileName}`).toBe(true); + } + }); + + it("keeps non-English locales in sync with en.json keys", () => { + const reference = JSON.parse(readFileSync(join(LOCALES_DIR, REFERENCE), "utf-8")) as Nested; + const referenceKeys = new Set(collectKeys(reference)); + + const localeFiles = readdirSync(LOCALES_DIR).filter( + (file) => file.endsWith(".json") && file !== REFERENCE, + ); + + for (const file of localeFiles) { + const content = JSON.parse(readFileSync(join(LOCALES_DIR, file), "utf-8")) as Nested; + const keys = new Set(collectKeys(content)); + const missing = [...referenceKeys].filter((key) => !keys.has(key)); + const extra = [...keys].filter((key) => !referenceKeys.has(key)); + expect(missing, `${file} missing keys`).toEqual([]); + expect(extra, `${file} extra keys`).toEqual([]); + } + }); +}); diff --git a/vite.config.ts b/vite.config.ts index 66374d7e8..a3da5cb0b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -36,6 +36,22 @@ export default defineConfig({ "zizmor:fix": { command: "zizmor --pedantic --fix .", }, + "i18n:check": { + command: "node --experimental-transform-types scripts/compare-translations.ts", + // Off because the script rewrites locale files (never cacheable), and + // caching triggers a vp spawn failure on the CI arm runner. + // See https://github.com/voidzero-dev/vite-task/issues/506 + cache: false, + }, + "i18n:report": { + command: "node --experimental-transform-types scripts/find-invalid-translations.ts", + }, + "i18n:schema": { + command: "node --experimental-transform-types scripts/generate-i18n-schema.ts", + }, + "build:lunaria": { + command: "node --experimental-transform-types ./lunaria/lunaria.ts", + }, }, }, lint: { @@ -480,6 +496,8 @@ export default defineConfig({ ], }, staged: { + "i18n/locales/*": + "node --experimental-transform-types ./lunaria/lunaria.ts && node --experimental-transform-types scripts/generate-i18n-schema.ts && vp fmt i18n/schema.json && git add i18n/schema.json", "*.{js,ts,mjs,cjs,vue}": "vp lint --fix", "*.{js,ts,mjs,cjs,vue,json,yml,md,html,css}": (files: string[]) => { const filtered = files.filter(