From 40e1562850b4ef2308d3dc865b44e35e617c7331 Mon Sep 17 00:00:00 2001 From: SB Yoon <44089734+yansigit@users.noreply.github.com> Date: Sun, 9 Aug 2026 17:27:18 -0600 Subject: [PATCH] fix(i18n): re-escape CC discovery-alias angle brackets for next-intl (#9917) * fix(i18n): re-escape CC discovery-alias angle brackets for next-intl Restore #8747 HTML-entity escaping for claude// in the three CC discovery-alias message keys so next-intl stops logging INVALID_MESSAGE: UNCLOSED_TAG on provider detail pages after the bulk entity-unescape regression. Co-authored-by: Cursor * chore(i18n): align conflict context with release * fix(i18n): cover localized CC alias placeholders --------- Co-authored-by: Cursor Co-authored-by: diegosouzapw Co-authored-by: Diego Rodrigues de Sa e Souza --- .../fixes/i18n-cc-alias-unclosed-tags.md | 1 + src/i18n/messages/en.json | 6 +- src/i18n/messages/fr.json | 6 +- src/i18n/messages/it.json | 6 +- src/i18n/messages/pt-BR.json | 6 +- src/i18n/messages/ru.json | 6 +- src/i18n/messages/vi.json | 6 +- .../unit/i18n-cc-alias-unclosed-tags.test.ts | 124 ++++++++++++++++++ 8 files changed, 143 insertions(+), 18 deletions(-) create mode 100644 changelog.d/fixes/i18n-cc-alias-unclosed-tags.md create mode 100644 tests/unit/i18n-cc-alias-unclosed-tags.test.ts diff --git a/changelog.d/fixes/i18n-cc-alias-unclosed-tags.md b/changelog.d/fixes/i18n-cc-alias-unclosed-tags.md new file mode 100644 index 0000000000..ed1a1b5de3 --- /dev/null +++ b/changelog.d/fixes/i18n-cc-alias-unclosed-tags.md @@ -0,0 +1 @@ +- fix(i18n): re-escape CC discovery-alias `claude//` to HTML entities so next-intl stops logging INVALID_MESSAGE: UNCLOSED_TAG on provider detail pages (#8747 regression) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 0dad760599..6722c0c181 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -937,7 +937,7 @@ "disabled": "Disabled", "featureFlagOmnirouteEmergencyFallbackDescription": "Route budget-exhausted requests to the emergency free fallback provider/model.", "featureFlagArenaEloSyncEnabledDescription": "Enable periodic Arena AI leaderboard ELO sync for model intelligence rankings.", - "featureFlagExposeCcDiscoveryAliasesDescription": "Advertise claude// mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.", + "featureFlagExposeCcDiscoveryAliasesDescription": "Advertise claude/<provider>/<model> mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.", "sidebar": { "home": "Home", "dashboard": "Dashboard", @@ -2516,7 +2516,7 @@ "always": "Always" }, "ccDiscoveryInfoButton": "How to enable discovery in Claude Code", - "ccDiscoveryInfoTooltip": "Advertise non-Claude models under claude// mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.", + "ccDiscoveryInfoTooltip": "Advertise non-Claude models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.", "ccDiscoveryInfoLink": "Open Feature Flags", "ccOnboardingTitle": "settings.json for gateway model discovery", "ccOnboardingCopy": "Copy", @@ -5206,7 +5206,7 @@ "interceptionLoadError": "Failed to load interception settings: {error}", "interceptionSaveError": "Failed to save interception settings: {error}", "ccAliasSectionTitle": "Expose in Claude Code (claude/…)", - "ccAliasSectionHint": "Advertise this provider's models under claude// mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.", + "ccAliasSectionHint": "Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.", "ccAliasProviderLevelLabel": "Provider default", "ccAliasModelOverridesLabel": "Per-model overrides", "ccAliasModelOverrideAriaLabel": "Override for {modelId}", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index d8b4e78ef0..6de90eeb37 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -937,7 +937,7 @@ "disabled": "Désactivé", "featureFlagOmnirouteEmergencyFallbackDescription": "Router les requêtes ayant épuisé leur budget vers le fournisseur/modèle de secours gratuit d'urgence.", "featureFlagArenaEloSyncEnabledDescription": "Activer la synchronisation périodique de l'ELO du classement Arena AI pour les classements d'intelligence des modèles.", - "featureFlagExposeCcDiscoveryAliasesDescription": "Afficher les identifiants miroir claude// dans /v1/models afin que la découverte de modèles de la passerelle Claude Code répertorie les modèles non-Claude. Attention : cette option double les entrées du catalogue pour tous les clients lorsqu'elle est activée globalement.", + "featureFlagExposeCcDiscoveryAliasesDescription": "Afficher les identifiants miroir claude/<provider>/<model> dans /v1/models afin que la découverte de modèles de la passerelle Claude Code répertorie les modèles non-Claude. Attention : cette option double les entrées du catalogue pour tous les clients lorsqu'elle est activée globalement.", "sidebar": { "home": "Accueil", "dashboard": "Tableau de bord", @@ -2516,7 +2516,7 @@ "always": "Toujours" }, "ccDiscoveryInfoButton": "Comment activer la découverte dans Claude Code", - "ccDiscoveryInfoTooltip": "Afficher les modèles non-Claude sous forme d'identifiants miroir claude// afin que la découverte de modèles de la passerelle Claude Code puisse les répertorier. Cette option double les entrées du catalogue pour tous les clients lorsqu'elle est activée globalement.", + "ccDiscoveryInfoTooltip": "Afficher les modèles non-Claude sous forme d'identifiants miroir claude/<provider>/<model> afin que la découverte de modèles de la passerelle Claude Code puisse les répertorier. Cette option double les entrées du catalogue pour tous les clients lorsqu'elle est activée globalement.", "ccDiscoveryInfoLink": "Ouvrir les indicateurs de fonctionnalités", "ccOnboardingTitle": "settings.json pour la découverte des modèles de la passerelle", "ccOnboardingCopy": "Copier", @@ -5206,7 +5206,7 @@ "interceptionLoadError": "Échec du chargement des paramètres d'interception : {error}", "interceptionSaveError": "Échec de l'enregistrement des paramètres d'interception : {error}", "ccAliasSectionTitle": "Exposer dans Claude Code (claude/…)", - "ccAliasSectionHint": "Afficher les modèles de ce fournisseur sous forme d'identifiants miroir claude// afin que la découverte de modèles de la passerelle Claude Code puisse les répertorier. Cette option est désactivée par défaut ; son activation double les entrées du catalogue pour tous les clients.", + "ccAliasSectionHint": "Afficher les modèles de ce fournisseur sous forme d'identifiants miroir claude/<provider>/<model> afin que la découverte de modèles de la passerelle Claude Code puisse les répertorier. Cette option est désactivée par défaut ; son activation double les entrées du catalogue pour tous les clients.", "ccAliasProviderLevelLabel": "Valeur par défaut du fournisseur", "ccAliasModelOverridesLabel": "Surcharges par modèle", "ccAliasModelOverrideAriaLabel": "Surcharge pour {modelId}", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 6802980f58..cd232a6c4b 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -937,7 +937,7 @@ "disabled": "Disabilitato", "featureFlagOmnirouteEmergencyFallbackDescription": "Indirizza le richieste con budget esaurito al provider/modello di fallback gratuito di emergenza.", "featureFlagArenaEloSyncEnabledDescription": "Abilita la sincronizzazione periodica dell'ELO della classifica Arena AI per le graduatorie di intelligenza dei modelli.", - "featureFlagExposeCcDiscoveryAliasesDescription": "Mostra gli id specchio claude// su /v1/models in modo che la scoperta dei modelli gateway di Claude Code elenchi i modelli non-Claude. Attenzione: raddoppia le voci nel catalogo per tutti i client quando abilitato globalmente.", + "featureFlagExposeCcDiscoveryAliasesDescription": "Mostra gli id specchio claude/<provider>/<model> su /v1/models in modo che la scoperta dei modelli gateway di Claude Code elenchi i modelli non-Claude. Attenzione: raddoppia le voci nel catalogo per tutti i client quando abilitato globalmente.", "sidebar": { "home": "Casa", "dashboard": "Pannello di controllo", @@ -2516,7 +2516,7 @@ "always": "Sempre" }, "ccDiscoveryInfoButton": "Come abilitare la scoperta in Claude Code", - "ccDiscoveryInfoTooltip": "Mostra i modelli non-Claude sotto gli id specchio claude// così la scoperta modelli gateway di Claude Code può elencarli. Raddoppia le voci nel catalogo per tutti i client quando abilitato globalmente.", + "ccDiscoveryInfoTooltip": "Mostra i modelli non-Claude sotto gli id specchio claude/<provider>/<model> così la scoperta modelli gateway di Claude Code può elencarli. Raddoppia le voci nel catalogo per tutti i client quando abilitato globalmente.", "ccDiscoveryInfoLink": "Apri le Flag Funzionalità", "ccOnboardingTitle": "settings.json per la scoperta modelli gateway", "ccOnboardingCopy": "Copia", @@ -5206,7 +5206,7 @@ "interceptionLoadError": "Impossibile caricare le impostazioni di intercettazione: {error}", "interceptionSaveError": "Impossibile salvare le impostazioni di intercettazione: {error}", "ccAliasSectionTitle": "Esponi in Claude Code (claude/…)", - "ccAliasSectionHint": "Mostra i modelli di questo provider sotto gli id specchio claude// così la scoperta modelli gateway di Claude Code può elencarli. Disattivato per impostazione predefinita — abilitarlo raddoppia le voci nel catalogo per tutti i client.", + "ccAliasSectionHint": "Mostra i modelli di questo provider sotto gli id specchio claude/<provider>/<model> così la scoperta modelli gateway di Claude Code può elencarli. Disattivato per impostazione predefinita — abilitarlo raddoppia le voci nel catalogo per tutti i client.", "ccAliasProviderLevelLabel": "Impostazione predefinita del provider", "ccAliasModelOverridesLabel": "Sostituzioni per modello", "ccAliasModelOverrideAriaLabel": "Sostituzione per {modelId}", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 25f35a1048..b1d5d0a858 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -937,7 +937,7 @@ "disabled": "Desabilitado", "featureFlagOmnirouteEmergencyFallbackDescription": "Encaminhar solicitações com orçamento esgotado para o provedor/modelo de fallback gratuito de emergência.", "featureFlagArenaEloSyncEnabledDescription": "Habilitar sincronização periódica de ELO da tabela de classificação do Arena AI para rankings de inteligência de modelos.", - "featureFlagExposeCcDiscoveryAliasesDescription": "Divulgar ids espelho claude// em /v1/models para que a descoberta de modelos do gateway Claude Code liste modelos não-Claude. Atenção: duplica as entradas do catálogo para todos os clientes quando ativado globalmente.", + "featureFlagExposeCcDiscoveryAliasesDescription": "Divulgar ids espelho claude/<provider>/<model> em /v1/models para que a descoberta de modelos do gateway Claude Code liste modelos não-Claude. Atenção: duplica as entradas do catálogo para todos os clientes quando ativado globalmente.", "sidebar": { "home": "Início", "dashboard": "Painel", @@ -2516,7 +2516,7 @@ "always": "Sempre" }, "ccDiscoveryInfoButton": "__MISSING__:How to enable discovery in Claude Code", - "ccDiscoveryInfoTooltip": "__MISSING__:Advertise non-Claude models under claude// mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.", + "ccDiscoveryInfoTooltip": "__MISSING__:Advertise non-Claude models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.", "ccDiscoveryInfoLink": "__MISSING__:Open Feature Flags", "ccOnboardingTitle": "settings.json para descoberta de modelos pelo gateway", "ccOnboardingCopy": "Copiar", @@ -5206,7 +5206,7 @@ "interceptionLoadError": "Falha ao carregar configuração de interceptação: {error}", "interceptionSaveError": "Falha ao salvar configuração de interceptação: {error}", "ccAliasSectionTitle": "__MISSING__:Expose in Claude Code (claude/…)", - "ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude// mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.", + "ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.", "ccAliasProviderLevelLabel": "__MISSING__:Provider default", "ccAliasModelOverridesLabel": "__MISSING__:Per-model overrides", "ccAliasModelOverrideAriaLabel": "__MISSING__:Override for {modelId}", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 2f68901c5a..56681691ab 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -937,7 +937,7 @@ "disabled": "Отключено", "featureFlagOmnirouteEmergencyFallbackDescription": "Перенаправлять запросы при исчерпании бюджета на резервный бесплатный провайдер/модель.", "featureFlagArenaEloSyncEnabledDescription": "Включить периодическую синхронизацию ELO из таблицы лидеров Arena AI для рейтинга интеллектуальности моделей.", - "featureFlagExposeCcDiscoveryAliasesDescription": "Публиковать claude/<провайдер>/<модель> зеркальные ID на /v1/models, чтобы Claude Code мог видеть не-Claude модели. Внимание: удваивает записи каталога для всех клиентов при глобальном включении.", + "featureFlagExposeCcDiscoveryAliasesDescription": "Публиковать claude/<провайдер>/<модель> зеркальные ID на /v1/models, чтобы Claude Code мог видеть не-Claude модели. Внимание: удваивает записи каталога для всех клиентов при глобальном включении.", "sidebar": { "home": "Главная", "dashboard": "Панель управления", @@ -2516,7 +2516,7 @@ "always": "Всегда" }, "ccDiscoveryInfoButton": "Как включить обнаружение в Claude Code", - "ccDiscoveryInfoTooltip": "Публиковать не-Claude модели под зеркальными ID claude/<провайдер>/<модель>, чтобы Claude Code мог их обнаруживать. Удваивает записи каталога для всех клиентов при глобальном включении.", + "ccDiscoveryInfoTooltip": "Публиковать не-Claude модели под зеркальными ID claude/<провайдер>/<модель>, чтобы Claude Code мог их обнаруживать. Удваивает записи каталога для всех клиентов при глобальном включении.", "ccDiscoveryInfoLink": "Открыть Feature Flags", "ccOnboardingTitle": "settings.json для обнаружения gateway моделей", "ccOnboardingCopy": "Копировать", @@ -5206,7 +5206,7 @@ "interceptionLoadError": "Не удалось загрузить настройки перехвата: {error}", "interceptionSaveError": "Не удалось сохранить настройки перехвата: {error}", "ccAliasSectionTitle": "Зеркальные ID Claude Code", - "ccAliasSectionHint": "Эти зеркала публикуют не-Claude модели под ID claude/<провайдер>/<модель>, чтобы Claude Code gateway model discovery мог их перечислить.", + "ccAliasSectionHint": "Эти зеркала публикуют не-Claude модели под ID claude/<провайдер>/<модель>, чтобы Claude Code gateway model discovery мог их перечислить.", "ccAliasProviderLevelLabel": "Провайдер включён", "ccAliasModelOverridesLabel": "Переопределения моделей", "ccAliasModelOverrideAriaLabel": "Переопределение модели", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 2ecef814fc..228d50b67d 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -937,7 +937,7 @@ "disabled": "Đã tắt", "featureFlagOmnirouteEmergencyFallbackDescription": "Định tuyến các yêu cầu đã hết ngân sách đến nhà cung cấp/mô hình dự phòng khẩn cấp miễn phí.", "featureFlagArenaEloSyncEnabledDescription": "Bật đồng bộ ELO định kỳ từ bảng xếp hạng Arena AI để xếp hạng năng lực của mô hình.", - "featureFlagExposeCcDiscoveryAliasesDescription": "Quảng bá các id phản chiếu claude// trên /v1/models để tính năng khám phá mô hình qua gateway của Claude Code liệt kê được các mô hình không phải Claude. Cảnh báo: khi bật ở phạm vi toàn cục, số mục trong danh mục tăng gấp đôi với mọi client.", + "featureFlagExposeCcDiscoveryAliasesDescription": "Quảng bá các id phản chiếu claude/<provider>/<model> trên /v1/models để tính năng khám phá mô hình qua gateway của Claude Code liệt kê được các mô hình không phải Claude. Cảnh báo: khi bật ở phạm vi toàn cục, số mục trong danh mục tăng gấp đôi với mọi client.", "sidebar": { "home": "Trang chủ", "dashboard": "Trang tổng quan", @@ -2516,7 +2516,7 @@ "always": "Luôn bật" }, "ccDiscoveryInfoButton": "Cách bật khám phá mô hình trong Claude Code", - "ccDiscoveryInfoTooltip": "Công bố các mô hình không phải Claude dưới dạng id phản chiếu claude/<provider>/<model> để tính năng khám phá mô hình qua gateway của Claude Code có thể liệt kê chúng. Khi bật ở phạm vi toàn cục, số mục trong danh mục tăng gấp đôi với mọi client.", + "ccDiscoveryInfoTooltip": "Quảng bá các mô hình không phải Claude dưới dạng id phản chiếu claude/<provider>/<model> để tính năng khám phá mô hình qua gateway của Claude Code có thể liệt kê chúng. Khi bật ở phạm vi toàn cục, số mục trong danh mục tăng gấp đôi với mọi client.", "ccDiscoveryInfoLink": "Mở Feature Flags", "ccOnboardingTitle": "settings.json cho việc khám phá mô hình qua gateway", "ccOnboardingCopy": "Sao chép", @@ -5204,7 +5204,7 @@ "interceptionLoadError": "Không thể tải cài đặt chặn: {error}", "interceptionSaveError": "Không thể lưu cài đặt chặn: {error}", "ccAliasSectionTitle": "Hiển thị trong Claude Code (claude/…)", - "ccAliasSectionHint": "Quảng bá các mô hình của nhà cung cấp này dưới dạng id phản chiếu claude// để tính năng khám phá mô hình qua gateway của Claude Code có thể liệt kê chúng. Mặc định tắt — bật lên sẽ nhân đôi số mục trong danh mục với mọi client.", + "ccAliasSectionHint": "Quảng bá các mô hình của nhà cung cấp này dưới dạng id phản chiếu claude/<provider>/<model> để tính năng khám phá mô hình qua gateway của Claude Code có thể liệt kê chúng. Mặc định tắt — bật lên sẽ nhân đôi số mục trong danh mục với mọi client.", "ccAliasProviderLevelLabel": "Mặc định của nhà cung cấp", "ccAliasModelOverridesLabel": "Ghi đè theo từng mô hình", "ccAliasModelOverrideAriaLabel": "Ghi đè cho {modelId}", diff --git a/tests/unit/i18n-cc-alias-unclosed-tags.test.ts b/tests/unit/i18n-cc-alias-unclosed-tags.test.ts new file mode 100644 index 0000000000..8fa5203ebc --- /dev/null +++ b/tests/unit/i18n-cc-alias-unclosed-tags.test.ts @@ -0,0 +1,124 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { readdirSync, readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createTranslator } from "next-intl"; + +/** + * Regression guard for next-intl INVALID_MESSAGE: UNCLOSED_TAG on CC + * discovery-alias copy (`claude//`). + * + * #8747 escaped these angle brackets to HTML entities. A later bulk + * entity-unescape reintroduced raw tags; next-intl treats them as rich-text + * tags and logs UNCLOSED_TAG on provider detail pages. + */ + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const MESSAGES_DIR = path.resolve(__dirname, "..", "..", "src", "i18n", "messages"); +const RAW_PATTERN = "claude//"; +const RAW_ALIAS_PATTERN = /claude\/<[^>\n]+>\/<[^>\n]+>/; +const ESCAPED_PATTERN = "claude/<provider>/<model>"; + +function localeFiles(): string[] { + return readdirSync(MESSAGES_DIR) + .filter((f) => f.endsWith(".json")) + .sort(); +} + +function readLocale(file: string): Record { + return JSON.parse(readFileSync(path.join(MESSAGES_DIR, file), "utf8")) as Record; +} + +test("no locale message file contains raw claude// in any language", () => { + const offenders: string[] = []; + for (const file of localeFiles()) { + const text = readFileSync(path.join(MESSAGES_DIR, file), "utf8"); + if (RAW_ALIAS_PATTERN.test(text)) { + offenders.push(file); + } + } + assert.equal( + offenders.length, + 0, + `Raw angle-bracket path must be HTML-entity escaped (#8747 regression):\n${offenders.join("\n")}` + ); +}); + +test("en.json CC discovery-alias keys use HTML-entity escaped path", () => { + const en = readLocale("en.json"); + const providers = (en.providers ?? {}) as Record; + const cliTools = (en.cliTools ?? {}) as Record; + const keys: Array<{ label: string; value: unknown }> = [ + { + label: "featureFlagExposeCcDiscoveryAliasesDescription", + value: en.featureFlagExposeCcDiscoveryAliasesDescription, + }, + { label: "cliTools.ccDiscoveryInfoTooltip", value: cliTools.ccDiscoveryInfoTooltip }, + { label: "providers.ccAliasSectionHint", value: providers.ccAliasSectionHint }, + ]; + + for (const { label, value } of keys) { + assert.equal(typeof value, "string", `${label} must be a string`); + assert.ok( + (value as string).includes(ESCAPED_PATTERN), + `${label} must contain ${ESCAPED_PATTERN}` + ); + assert.equal( + (value as string).includes(RAW_PATTERN), + false, + `${label} must not contain raw ${RAW_PATTERN}` + ); + } +}); + +test("createTranslator accepts CC discovery-alias keys in every locale", () => { + const errors: Array<{ + locale: string; + code?: string; + originalMessage?: string; + message?: string; + }> = []; + + for (const file of localeFiles()) { + const locale = file.replace(/\.json$/, ""); + const messages = readLocale(file); + const onError = (err: unknown) => { + errors.push({ + locale, + ...(err as { code?: string; originalMessage?: string; message?: string }), + }); + }; + + const tProviders = createTranslator({ + locale, + messages, + namespace: "providers", + onError, + }); + const tCliTools = createTranslator({ + locale, + messages, + namespace: "cliTools", + onError, + }); + const tRoot = createTranslator({ locale, messages, onError }); + + assert.ok(tProviders("ccAliasSectionHint").length > 0); + assert.ok(tCliTools("ccDiscoveryInfoTooltip").length > 0); + assert.ok(tRoot("featureFlagExposeCcDiscoveryAliasesDescription").length > 0); + } + + const bad = errors.filter( + (e) => + e.code === "INVALID_MESSAGE" || + String(e.originalMessage ?? e.message ?? "").includes("UNCLOSED_TAG") + ); + assert.equal( + bad.length, + 0, + `next-intl INVALID_MESSAGE/UNCLOSED_TAG on CC alias keys:\n${bad + .map((e) => `${e.locale}: ${e.code}: ${e.originalMessage ?? e.message}`) + .join("\n")}` + ); +});