This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new f0a578a75 test(web): fail when a translation key is missing or 
incomplete (#4809)
f0a578a75 is described below

commit f0a578a75c651eed93678926f56369200fb61cf2
Author: Wang1rrr <[email protected]>
AuthorDate: Thu Sep 24 17:44:28 2026 +0800

    test(web): fail when a translation key is missing or incomplete (#4809)
    
    test(web): fail when a translation key is missing or incomplete
    
    t() returns the key itself when the dictionary has no entry, so a 
misspelled, renamed or
    dropped key renders as raw text such as "audit.exportFailed" in the console 
while the whole
    suite still passes. Nothing in the type system connects a t('...') call to 
an entry, because
    the dictionary is keyed by string.
    
    Add the two invariants that make that failure loud:
    
    - every entry carries a non-empty zh and en value, so a half-added key 
cannot ship a blank
      label in one locale
    - every literal key that any non-test source hands to t() resolves. The 
sources are scanned
      as text rather than walked as modules, so branches no test renders are 
covered too.
    
    Template keys are out of scope for a static scan; the composed families 
they draw from are
    already covered by theme.test.ts and the audit presentation lookups.
    
    Both cases were verified to fail when broken: pointing a probe file at
    'definitely.not.a.real.key' fails the second, and blanking the en value of 
common.close
    fails the first.
    
    The scan currently covers 1881 literal t() call sites across 183 non-test 
source files
    against 2177 dictionary entries, and all of them resolve.
---
 web/src/i18n/__tests__/translations.test.ts | 65 +++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)

diff --git a/web/src/i18n/__tests__/translations.test.ts 
b/web/src/i18n/__tests__/translations.test.ts
new file mode 100644
index 000000000..5f3c1988d
--- /dev/null
+++ b/web/src/i18n/__tests__/translations.test.ts
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, expect, it } from 'vitest';
+import translations from '../translations';
+
+/**
+ * The dictionary is looked up by string, so nothing in the type system 
connects a `t('...')`
+ * call to an entry. A key that is misspelled, renamed or dropped during a 
refactor renders as
+ * the raw key - `t` falls back to returning it - and the whole suite still 
passes. These two
+ * invariants are what makes that failure loud.
+ *
+ * The sources are read as text rather than walked as modules because the 
point is to see the
+ * keys a file *asks for*, including in branches no test renders.
+ */
+const sources = import.meta.glob('../../**/*.{ts,tsx}', {
+  query: '?raw',
+  import: 'default',
+  eager: true,
+}) as Record<string, string>;
+
+/** A literal key handed to `t`. Template keys such as `` 
t(`audit.op.${code}`) `` are composed
+ * from values that only exist at runtime, so they are out of scope for a 
static scan. */
+const LITERAL_KEY = /\bt\(\s*'([^']+)'/g;
+
+describe('translations dictionary', () => {
+  it('gives every entry a non-empty Chinese and an English value', () => {
+    const incomplete = Object.entries(translations)
+      .filter(([, value]) => !value.zh.trim() || !value.en.trim())
+      .map(([key]) => key);
+
+    expect(incomplete, 'entries missing a locale').toEqual([]);
+  });
+
+  it('resolves every literal key the sources ask t() for', () => {
+    const missing = new Map<string, string[]>();
+
+    for (const [path, source] of Object.entries(sources)) {
+      // Test files mock the language context and invent keys on purpose.
+      if (path.includes('.test.')) continue;
+      for (const match of source.matchAll(LITERAL_KEY)) {
+        const key = match[1];
+        if (key in translations) continue;
+        missing.set(key, [...(missing.get(key) ?? []), path]);
+      }
+    }
+
+    const detail = [...missing].map(([key, paths]) => `${key} <- 
${paths.join(', ')}`);
+    expect(detail, 'literal keys with no dictionary entry').toEqual([]);
+  });
+});

Reply via email to