From fe01a5a28f34c873019ae3c34086acd6bd791a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:27:44 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20locales=E3=82=92workspace?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=B8=8B=E3=81=AE=E3=83=91=E3=83=83=E3=82=B1?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=81=AB=20(#16895)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: localesをworkspace管理下のパッケージに * fix copilot review * move * move * rename * fix ci * revert unwanted indent changes * fix * fix * fix * fix * 間違えてコミットしていたのを戻す * 不要 * 追加漏れ * ymlの場所だけ戻す * localesの位置を戻したのでこの差分は不要 * 内容的にlocalesにある方が正しい * i18nパッケージ用のREADME.mdを用意 * fix locale.yml * fix locale.yml --------- Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> --- .github/workflows/lint.yml | 7 +- .github/workflows/locale.yml | 29 ++- Dockerfile | 2 + locales/generateDTS.js | 232 ------------------ locales/index.js | 93 ------- locales/package.json | 3 - locales/verify.js | 53 ---- package.json | 17 +- .../backend/src/core/NotificationService.ts | 4 +- packages/frontend-builder/locale-inliner.ts | 2 +- .../locale-inliner/apply-with-locale.ts | 2 +- packages/frontend-builder/package.json | 1 + packages/frontend-embed/build.ts | 2 +- packages/frontend-embed/package.json | 1 + .../frontend-embed/src/components/I18n.vue | 4 +- packages/frontend-embed/src/i18n.ts | 2 +- packages/frontend-embed/vite.config.ts | 4 +- packages/frontend-shared/js/i18n.ts | 2 +- packages/frontend-shared/js/locale.ts | 2 +- .../frontend-shared/js/store-boot-errors.ts | 2 +- packages/frontend-shared/package.json | 1 + .../frontend/.storybook/preload-locale.ts | 2 +- packages/frontend/build.ts | 2 +- .../frontend/lib/vite-plugin-watch-locales.ts | 18 +- packages/frontend/package.json | 1 + .../frontend/src/components/global/I18n.vue | 4 +- packages/frontend/src/i18n.ts | 2 +- packages/frontend/test/i18n.test.ts | 2 +- packages/frontend/test/init.ts | 6 +- packages/frontend/vite.config.ts | 6 +- packages/i18n/README.md | 5 + packages/i18n/build.ts | 163 ++++++++++++ packages/i18n/eslint.config.js | 35 +++ packages/i18n/package.json | 42 ++++ .../i18n/scripts/generateLocaleInterface.ts | 153 ++++++++++++ packages/i18n/scripts/verify.ts | 70 ++++++ .../i18n/src/autogen/locale.ts | 15 +- packages/i18n/src/index.ts | 166 +++++++++++++ packages/i18n/src/types.ts | 14 ++ packages/i18n/tsconfig.eslint.json | 8 + packages/i18n/tsconfig.json | 14 ++ packages/sw/build.js | 3 +- packages/sw/package.json | 1 + packages/sw/src/scripts/lang.ts | 2 +- packages/sw/src/sw.ts | 2 +- pnpm-lock.yaml | 58 +++++ pnpm-workspace.yaml | 1 + scripts/build-assets.mjs | 97 +++----- scripts/clean-all.js | 3 + scripts/clean.js | 1 + scripts/dev.mjs | 18 ++ 51 files changed, 860 insertions(+), 519 deletions(-) delete mode 100644 locales/generateDTS.js delete mode 100644 locales/index.js delete mode 100644 locales/package.json delete mode 100644 locales/verify.js create mode 100644 packages/i18n/README.md create mode 100644 packages/i18n/build.ts create mode 100644 packages/i18n/eslint.config.js create mode 100644 packages/i18n/package.json create mode 100644 packages/i18n/scripts/generateLocaleInterface.ts create mode 100644 packages/i18n/scripts/verify.ts rename locales/index.d.ts => packages/i18n/src/autogen/locale.ts (99%) create mode 100644 packages/i18n/src/index.ts create mode 100644 packages/i18n/src/types.ts create mode 100644 packages/i18n/tsconfig.eslint.json create mode 100644 packages/i18n/tsconfig.json diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d80c56f032..33a1ccbc76 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -111,10 +111,5 @@ jobs: node-version-file: '.node-version' cache: 'pnpm' - run: pnpm i --frozen-lockfile - - run: pnpm --filter misskey-js run build - if: ${{ matrix.workspace == 'backend' || matrix.workspace == 'frontend' || matrix.workspace == 'sw' }} - - run: pnpm --filter misskey-reversi run build - if: ${{ matrix.workspace == 'backend' || matrix.workspace == 'frontend' }} - - run: pnpm --filter misskey-bubble-game run build - if: ${{ matrix.workspace == 'frontend' }} + - run: pnpm --filter "${{ matrix.workspace }}^..." run build - run: pnpm --filter ${{ matrix.workspace }} run typecheck diff --git a/.github/workflows/locale.yml b/.github/workflows/locale.yml index e63d83997b..d75335f38f 100644 --- a/.github/workflows/locale.yml +++ b/.github/workflows/locale.yml @@ -3,10 +3,12 @@ name: Lint on: push: paths: + - packages/i18n/** - locales/** - .github/workflows/locale.yml pull_request: paths: + - packages/i18n/** - locales/** - .github/workflows/locale.yml jobs: @@ -14,15 +16,18 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4.3.0 - with: - fetch-depth: 0 - submodules: true - - name: Setup pnpm - uses: pnpm/action-setup@v4.2.0 - - uses: actions/setup-node@v4.4.0 - with: - node-version-file: '.node-version' - cache: 'pnpm' - - run: pnpm i --frozen-lockfile - - run: cd locales && node verify.js + - uses: actions/checkout@v4.3.0 + with: + fetch-depth: 0 + submodules: true + - name: Setup pnpm + uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v4.4.0 + with: + node-version-file: ".node-version" + cache: "pnpm" + - run: pnpm i --frozen-lockfile + - run: pnpm --filter i18n build + - name: Verify Locales + working-directory: ./packages/i18n + run: pnpm run verify diff --git a/Dockerfile b/Dockerfile index 20e24d1dc2..a071970927 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,7 @@ COPY --link ["packages/frontend-shared/package.json", "./packages/frontend-share COPY --link ["packages/frontend/package.json", "./packages/frontend/"] COPY --link ["packages/frontend-embed/package.json", "./packages/frontend-embed/"] COPY --link ["packages/frontend-builder/package.json", "./packages/frontend-builder/"] +COPY --link ["packages/i18n/package.json", "./packages/i18n/"] COPY --link ["packages/icons-subsetter/package.json", "./packages/icons-subsetter/"] COPY --link ["packages/sw/package.json", "./packages/sw/"] COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] @@ -101,6 +102,7 @@ COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-js/ COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-reversi/built ./packages/misskey-reversi/built COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-bubble-game/built ./packages/misskey-bubble-game/built COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built +COPY --chown=misskey:misskey --from=native-builder /misskey/packages/i18n/built ./packages/i18n/built COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis COPY --chown=misskey:misskey . ./ diff --git a/locales/generateDTS.js b/locales/generateDTS.js deleted file mode 100644 index ab0613cc82..0000000000 --- a/locales/generateDTS.js +++ /dev/null @@ -1,232 +0,0 @@ -import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import * as yaml from 'js-yaml'; -import ts from 'typescript'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const parameterRegExp = /\{(\w+)\}/g; - -function createMemberType(item) { - if (typeof item !== 'string') { - return ts.factory.createTypeLiteralNode(createMembers(item)); - } - const parameters = Array.from( - item.matchAll(parameterRegExp), - ([, parameter]) => parameter, - ); - return parameters.length - ? ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('ParameterizedString'), - [ - ts.factory.createUnionTypeNode( - parameters.map((parameter) => - ts.factory.createStringLiteral(parameter), - ), - ), - ], - ) - : ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); -} - -function createMembers(record) { - return Object.entries(record).map(([k, v]) => { - const node = ts.factory.createPropertySignature( - undefined, - ts.factory.createStringLiteral(k), - undefined, - createMemberType(v), - ); - if (typeof v === 'string') { - ts.addSyntheticLeadingComment( - node, - ts.SyntaxKind.MultiLineCommentTrivia, - `* - * ${v.replace(/\n/g, '\n * ')} - `, - true, - ); - } - return node; - }); -} - -export default function generateDTS() { - const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8')); - const members = createMembers(locale); - const elements = [ - ts.factory.createVariableStatement( - [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - ts.factory.createIdentifier('kParameters'), - undefined, - ts.factory.createTypeOperatorNode( - ts.SyntaxKind.UniqueKeyword, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword), - ), - undefined, - ), - ], - ts.NodeFlags.Const, - ), - ), - ts.factory.createTypeAliasDeclaration( - [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], - ts.factory.createIdentifier('ParameterizedString'), - [ - ts.factory.createTypeParameterDeclaration( - undefined, - ts.factory.createIdentifier('T'), - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - ), - ], - ts.factory.createIntersectionTypeNode([ - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - ts.factory.createTypeLiteralNode([ - ts.factory.createPropertySignature( - undefined, - ts.factory.createComputedPropertyName( - ts.factory.createIdentifier('kParameters'), - ), - undefined, - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('T'), - undefined, - ), - ), - ]) - ]), - ), - ts.factory.createInterfaceDeclaration( - [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], - ts.factory.createIdentifier('ILocale'), - undefined, - undefined, - [ - ts.factory.createIndexSignature( - undefined, - [ - ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createIdentifier('_'), - undefined, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - undefined, - ), - ], - ts.factory.createUnionTypeNode([ - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('ParameterizedString'), - ), - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('ILocale'), - undefined, - ), - ]), - ), - ], - ), - ts.factory.createInterfaceDeclaration( - [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], - ts.factory.createIdentifier('Locale'), - undefined, - [ - ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [ - ts.factory.createExpressionWithTypeArguments( - ts.factory.createIdentifier('ILocale'), - undefined, - ), - ]), - ], - members, - ), - ts.factory.createVariableStatement( - [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - ts.factory.createIdentifier('locales'), - undefined, - ts.factory.createTypeLiteralNode([ - ts.factory.createIndexSignature( - undefined, - [ - ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createIdentifier('lang'), - undefined, - ts.factory.createKeywordTypeNode( - ts.SyntaxKind.StringKeyword, - ), - undefined, - ), - ], - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('Locale'), - undefined, - ), - ), - ]), - undefined, - ), - ], - ts.NodeFlags.Const, - ), - ), - ts.factory.createFunctionDeclaration( - [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], - undefined, - ts.factory.createIdentifier('build'), - undefined, - [], - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('Locale'), - undefined, - ), - undefined, - ), - ts.factory.createExportDefault(ts.factory.createIdentifier('locales')), - ]; - ts.addSyntheticLeadingComment( - elements[0], - ts.SyntaxKind.MultiLineCommentTrivia, - ' eslint-disable ', - true, - ); - ts.addSyntheticLeadingComment( - elements[0], - ts.SyntaxKind.SingleLineCommentTrivia, - ' This file is generated by locales/generateDTS.js', - true, - ); - ts.addSyntheticLeadingComment( - elements[0], - ts.SyntaxKind.SingleLineCommentTrivia, - ' Do not edit this file directly.', - true, - ); - const printed = ts - .createPrinter({ - newLine: ts.NewLineKind.LineFeed, - }) - .printList( - ts.ListFormat.MultiLine, - ts.factory.createNodeArray(elements), - ts.createSourceFile( - 'index.d.ts', - '', - ts.ScriptTarget.ESNext, - true, - ts.ScriptKind.TS, - ), - ); - - fs.writeFileSync(`${__dirname}/index.d.ts`, printed, 'utf-8'); -} diff --git a/locales/index.js b/locales/index.js deleted file mode 100644 index 6d9cf4796b..0000000000 --- a/locales/index.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Languages Loader - */ - -import * as fs from 'node:fs'; -import * as yaml from 'js-yaml'; - -const merge = (...args) => args.reduce((a, c) => ({ - ...a, - ...c, - ...Object.entries(a) - .filter(([k]) => c && typeof c[k] === 'object') - .reduce((a, [k, v]) => (a[k] = merge(v, c[k]), a), {}) -}), {}); - -const languages = [ - 'ar-SA', - 'ca-ES', - 'cs-CZ', - 'da-DK', - 'de-DE', - 'en-US', - 'es-ES', - 'fr-FR', - 'id-ID', - 'it-IT', - 'ja-JP', - 'ja-KS', - 'kab-KAB', - 'kn-IN', - 'ko-KR', - 'nl-NL', - 'no-NO', - 'pl-PL', - 'pt-PT', - 'ru-RU', - 'sk-SK', - 'th-TH', - 'tr-TR', - 'ug-CN', - 'uk-UA', - 'vi-VN', - 'zh-CN', - 'zh-TW', -]; - -const primaries = { - 'en': 'US', - 'ja': 'JP', - 'zh': 'CN', -}; - -// 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く -const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), ''); - -export function build() { - // vitestの挙動を調整するため、一度ローカル変数化する必要がある - // https://github.com/vitest-dev/vitest/issues/3988#issuecomment-1686599577 - // https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785 - const metaUrl = import.meta.url; - const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {}); - - // 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す - const removeEmpty = (obj) => { - for (const [k, v] of Object.entries(obj)) { - if (v === '') { - delete obj[k]; - } else if (typeof v === 'object') { - removeEmpty(v); - } - } - return obj; - }; - removeEmpty(locales); - - return Object.entries(locales) - .reduce((a, [k, v]) => (a[k] = (() => { - const [lang] = k.split('-'); - switch (k) { - case 'ja-JP': return v; - case 'ja-KS': - case 'en-US': return merge(locales['ja-JP'], v); - default: return merge( - locales['ja-JP'], - locales['en-US'], - locales[`${lang}-${primaries[lang]}`] ?? {}, - v - ); - } - })(), a), {}); -} - -export default build(); diff --git a/locales/package.json b/locales/package.json deleted file mode 100644 index bedb411a91..0000000000 --- a/locales/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/locales/verify.js b/locales/verify.js deleted file mode 100644 index a8e9875d6e..0000000000 --- a/locales/verify.js +++ /dev/null @@ -1,53 +0,0 @@ -import locales from './index.js'; - -let valid = true; - -function writeError(type, lang, tree, data) { - process.stderr.write(JSON.stringify({ type, lang, tree, data })); - process.stderr.write('\n'); - valid = false; -} - -function verify(expected, actual, lang, trace) { - for (let key in expected) { - if (!Object.prototype.hasOwnProperty.call(actual, key)) { - continue; - } - if (typeof expected[key] === 'object') { - if (typeof actual[key] !== 'object') { - writeError('mismatched_type', lang, trace ? `${trace}.${key}` : key, { expected: 'object', actual: typeof actual[key] }); - continue; - } - verify(expected[key], actual[key], lang, trace ? `${trace}.${key}` : key); - } else if (typeof expected[key] === 'string') { - switch (typeof actual[key]) { - case 'object': - writeError('mismatched_type', lang, trace ? `${trace}.${key}` : key, { expected: 'string', actual: 'object' }); - break; - case 'undefined': - continue; - case 'string': - const expectedParameters = new Set(expected[key].match(/\{[^}]+\}/g)?.map((s) => s.slice(1, -1))); - const actualParameters = new Set(actual[key].match(/\{[^}]+\}/g)?.map((s) => s.slice(1, -1))); - for (let parameter of expectedParameters) { - if (!actualParameters.has(parameter)) { - writeError('missing_parameter', lang, trace ? `${trace}.${key}` : key, { parameter }); - } - } - } - } - } -} - -const { ['ja-JP']: original, ...verifiees } = locales; - -for (let lang in verifiees) { - if (!Object.prototype.hasOwnProperty.call(locales, lang)) { - continue; - } - verify(original, verifiees[lang], lang); -} - -if (!valid) { - process.exit(1); -} diff --git a/package.json b/package.json index 664c5e9e71..f11b24d0ad 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,17 @@ }, "packageManager": "pnpm@10.22.0", "workspaces": [ - "packages/frontend-shared", - "packages/frontend", - "packages/frontend-embed", - "packages/icons-subsetter", - "packages/backend", - "packages/sw", "packages/misskey-js", + "packages/i18n", "packages/misskey-reversi", - "packages/misskey-bubble-game" + "packages/misskey-bubble-game", + "packages/icons-subsetter", + "packages/frontend-shared", + "packages/frontend-builder", + "packages/sw", + "packages/backend", + "packages/frontend", + "packages/frontend-embed" ], "private": true, "scripts": { @@ -68,6 +70,7 @@ }, "devDependencies": { "@eslint/js": "9.39.1", + "i18n": "workspace:*", "@misskey-dev/eslint-plugin": "2.2.0", "@types/js-yaml": "4.0.9", "@types/node": "24.10.1", diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index eeade4569b..310ffec7ce 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -202,7 +202,7 @@ export class NotificationService implements OnApplicationShutdown { } // TODO - //const locales = await import('../../../../locales/index.js'); + //const locales = await import('i18n'); // TODO: locale ファイルをクライアント用とサーバー用で分けたい @@ -271,7 +271,7 @@ export class NotificationService implements OnApplicationShutdown { let untilTime = untilId ? this.toXListId(untilId) : null; let notifications: MiNotification[]; - for (;;) { + for (; ;) { let notificationsRes: [id: string, fields: string[]][]; // sinceidのみの場合は古い順、そうでない場合は新しい順。 QueryService.makePaginationQueryも参照 diff --git a/packages/frontend-builder/locale-inliner.ts b/packages/frontend-builder/locale-inliner.ts index 9bef465eeb..191d7250a6 100644 --- a/packages/frontend-builder/locale-inliner.ts +++ b/packages/frontend-builder/locale-inliner.ts @@ -10,7 +10,7 @@ import { collectModifications } from './locale-inliner/collect-modifications.js' import { applyWithLocale } from './locale-inliner/apply-with-locale.js'; import { blankLogger } from './logger.js'; import type { Logger } from './logger.js'; -import type { Locale } from '../../locales/index.js'; +import type { Locale } from 'i18n'; import type { Manifest as ViteManifest } from 'vite'; export class LocaleInliner { diff --git a/packages/frontend-builder/locale-inliner/apply-with-locale.ts b/packages/frontend-builder/locale-inliner/apply-with-locale.ts index 5e601cdf12..78851d3029 100644 --- a/packages/frontend-builder/locale-inliner/apply-with-locale.ts +++ b/packages/frontend-builder/locale-inliner/apply-with-locale.ts @@ -5,7 +5,7 @@ import MagicString from 'magic-string'; import { assertNever } from '../utils.js'; -import type { Locale, ILocale } from '../../../locales/index.js'; +import type { ILocale, Locale } from 'i18n'; import type { TextModification } from '../locale-inliner.js'; import type { Logger } from '../logger.js'; diff --git a/packages/frontend-builder/package.json b/packages/frontend-builder/package.json index d01e4c86ed..37dd133fe6 100644 --- a/packages/frontend-builder/package.json +++ b/packages/frontend-builder/package.json @@ -18,6 +18,7 @@ "typescript": "5.9.3" }, "dependencies": { + "i18n": "workspace:*", "estree-walker": "3.0.3", "magic-string": "0.30.21", "vite": "7.2.4" diff --git a/packages/frontend-embed/build.ts b/packages/frontend-embed/build.ts index 737233a4d0..4e1f588802 100644 --- a/packages/frontend-embed/build.ts +++ b/packages/frontend-embed/build.ts @@ -2,7 +2,7 @@ import * as fs from 'fs/promises'; import url from 'node:url'; import path from 'node:path'; import { execa } from 'execa'; -import locales from '../../locales/index.js'; +import locales from 'i18n'; import { LocaleInliner } from '../frontend-builder/locale-inliner.js' import { createLogger } from '../frontend-builder/logger'; diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index 85e25c8faa..c27583cf86 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@discordapp/twemoji": "16.0.1", + "i18n": "workspace:*", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.3", "@rollup/pluginutils": "5.3.0", diff --git a/packages/frontend-embed/src/components/I18n.vue b/packages/frontend-embed/src/components/I18n.vue index b621110ec9..9866e50958 100644 --- a/packages/frontend-embed/src/components/I18n.vue +++ b/packages/frontend-embed/src/components/I18n.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only