From 05199dda00035c3c95b60f005e68447ab08fe249 Mon Sep 17 00:00:00 2001
From: "Cheng Ho Ming, Eric" <123erichappy123@gmail.com>
Date: Mon, 2 Nov 2026 22:10:18 +0800
Subject: [PATCH] fix(routing): show the Balancer label on mobile rule cards
(#6960)
RuleCardList labelled balancer rules with t('pages.xray.balancer'), which is an object in every locale, so phones showed i18next's 'returned an object instead of string' error. It now uses the pages.xray.rules.balancer leaf the Balancers tab already uses, and the dead-keys test fails on any literal key that names a group.
---
frontend/src/pages/xray/routing/RuleCardList.tsx | 4 +---
frontend/src/test/i18n-dead-keys.test.ts | 13 ++++++++++++-
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/frontend/src/pages/xray/routing/RuleCardList.tsx b/frontend/src/pages/xray/routing/RuleCardList.tsx
index ffa43c267..50f2e925c 100644
--- a/frontend/src/pages/xray/routing/RuleCardList.tsx
+++ b/frontend/src/pages/xray/routing/RuleCardList.tsx
@@ -152,9 +152,7 @@ export default function RuleCardList({
→
- {rule.balancerTag
- ? t('pages.xray.balancer') || 'Balancer'
- : t('pages.xray.Outbounds')}
+ {rule.balancerTag ? t('pages.xray.rules.balancer') : t('pages.xray.Outbounds')}
{rule.outboundTag ? (
diff --git a/frontend/src/test/i18n-dead-keys.test.ts b/frontend/src/test/i18n-dead-keys.test.ts
index a8b595f64..002849c57 100644
--- a/frontend/src/test/i18n-dead-keys.test.ts
+++ b/frontend/src/test/i18n-dead-keys.test.ts
@@ -5,7 +5,7 @@ import { join, resolve } from 'node:path';
import { describe, expect, it } from 'vitest';
/*
- * Guards the 14-locale translation set two ways: every key in en-US must be
+ * Guards the 14-locale translation set: every key in en-US must be
* referenced somewhere in the frontend or Go sources (dead keys accumulate
* silently — this test deleted over two hundred of them when it was
* introduced), and every locale must carry exactly the en-US key set
@@ -82,6 +82,17 @@ describe('i18n keys', () => {
);
});
+ it('no literal t() key resolves to an object', () => {
+ const objectKeys = Array.from(
+ blob.matchAll(/\bt\(\s*(['"`])([\w.-]+)\1\s*[,)]/g),
+ (m) => m[2],
+ ).filter((key) => enKeys.some((leaf) => leaf.startsWith(`${key}.`)));
+ expect(
+ objectKeys,
+ `t() keys naming a group, rendered as "key '…' returned an object instead of string.":\n ${objectKeys.join('\n ')}`,
+ ).toEqual([]);
+ });
+
it('every locale carries exactly the en-US key set', () => {
const enSet = new Set(enKeys);
for (const file of readdirSync(translationDir)) {