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/<provider>/<model> 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 <cursoragent@cursor.com>

* chore(i18n): align conflict context with release

* fix(i18n): cover localized CC alias placeholders

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
This commit is contained in:
SB Yoon
2026-08-09 17:27:18 -06:00
committed by GitHub
parent fade1cbab3
commit 40e1562850
8 changed files with 143 additions and 18 deletions

View File

@@ -0,0 +1 @@
- fix(i18n): re-escape CC discovery-alias `claude/<provider>/<model>` to HTML entities so next-intl stops logging INVALID_MESSAGE: UNCLOSED_TAG on provider detail pages (#8747 regression)

View File

@@ -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/<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.",
"featureFlagExposeCcDiscoveryAliasesDescription": "Advertise claude/&lt;provider&gt;/&lt;model&gt; 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/<provider>/<model> 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/&lt;provider&gt;/&lt;model&gt; 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/<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.",
"ccAliasSectionHint": "Advertise this provider's models under claude/&lt;provider&gt;/&lt;model&gt; 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}",

View File

@@ -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/<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.",
"featureFlagExposeCcDiscoveryAliasesDescription": "Afficher les identifiants miroir claude/&lt;provider&gt;/&lt;model&gt; 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/<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.",
"ccDiscoveryInfoTooltip": "Afficher les modèles non-Claude sous forme d'identifiants miroir claude/&lt;provider&gt;/&lt;model&gt; 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/<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.",
"ccAliasSectionHint": "Afficher les modèles de ce fournisseur sous forme d'identifiants miroir claude/&lt;provider&gt;/&lt;model&gt; 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}",

View File

@@ -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/<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.",
"featureFlagExposeCcDiscoveryAliasesDescription": "Mostra gli id specchio claude/&lt;provider&gt;/&lt;model&gt; 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/<provider>/<model> 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/&lt;provider&gt;/&lt;model&gt; 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/<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.",
"ccAliasSectionHint": "Mostra i modelli di questo provider sotto gli id specchio claude/&lt;provider&gt;/&lt;model&gt; 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}",

View File

@@ -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/<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.",
"featureFlagExposeCcDiscoveryAliasesDescription": "Divulgar ids espelho claude/&lt;provider&gt;/&lt;model&gt; 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/<provider>/<model> 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/&lt;provider&gt;/&lt;model&gt; 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/<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.",
"ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude/&lt;provider&gt;/&lt;model&gt; 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}",

View File

@@ -937,7 +937,7 @@
"disabled": "Отключено",
"featureFlagOmnirouteEmergencyFallbackDescription": "Перенаправлять запросы при исчерпании бюджета на резервный бесплатный провайдер/модель.",
"featureFlagArenaEloSyncEnabledDescription": "Включить периодическую синхронизацию ELO из таблицы лидеров Arena AI для рейтинга интеллектуальности моделей.",
"featureFlagExposeCcDiscoveryAliasesDescription": "Публиковать claude/<провайдер>/<модель> зеркальные ID на /v1/models, чтобы Claude Code мог видеть не-Claude модели. Внимание: удваивает записи каталога для всех клиентов при глобальном включении.",
"featureFlagExposeCcDiscoveryAliasesDescription": "Публиковать claude/&lt;провайдер&gt;/&lt;модель&gt; зеркальные 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/&lt;провайдер&gt;/&lt;модель&gt;, чтобы 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/&lt;провайдер&gt;/&lt;модель&gt;, чтобы Claude Code gateway model discovery мог их перечислить.",
"ccAliasProviderLevelLabel": "Провайдер включён",
"ccAliasModelOverridesLabel": "Переопределения моделей",
"ccAliasModelOverrideAriaLabel": "Переопределение модели",

View File

@@ -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/<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.",
"featureFlagExposeCcDiscoveryAliasesDescription": "Quảng bá các id phản chiếu claude/&lt;provider&gt;/&lt;model&gt; 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": "ng b các mô hình không phải Claude dưới dạng id phản chiếu claude/&lt;provider&gt;/&lt;model&gt; để 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/&lt;provider&gt;/&lt;model&gt; để 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/<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.",
"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/&lt;provider&gt;/&lt;model&gt; để 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}",

View File

@@ -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/<provider>/<model>`).
*
* #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/<provider>/<model>";
const RAW_ALIAS_PATTERN = /claude\/<[^>\n]+>\/<[^>\n]+>/;
const ESCAPED_PATTERN = "claude/&lt;provider&gt;/&lt;model&gt;";
function localeFiles(): string[] {
return readdirSync(MESSAGES_DIR)
.filter((f) => f.endsWith(".json"))
.sort();
}
function readLocale(file: string): Record<string, unknown> {
return JSON.parse(readFileSync(path.join(MESSAGES_DIR, file), "utf8")) as Record<string, unknown>;
}
test("no locale message file contains raw claude/<provider>/<model> 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<string, unknown>;
const cliTools = (en.cliTools ?? {}) as Record<string, unknown>;
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")}`
);
});