Bugfix Issue 781 TLi18n TwoArg Throw - sebiboga/testlink-upgraded GitHub Wiki
Bugfix β Issue #781: TLi18n.t(key, 'default') throws 'Cannot use in operator' and aborts dashboard widget rendering
Issue: #781
Branch: fix/issue-781 Β· Commits: 1c0688b55 (fix), 41a744591 (regression suite + fixture), 306767447 (docs + screenshots)
Status: FIXED & VERIFIED (2026-08-31)
Symptom
A call to the client-side i18n helper passing a plain string as the second argument β
e.g. TLi18n.t("dash.generatedOn", "Generated on") β made the Dashboard
(gui/templates/mainpage/mainPage.html) throw before drawing any widget:
Uncaught TypeError: Cannot use 'in' operator to search for 'length' in "Generated on"
Result: execution-status table stays empty (#execTotal = 0), the Monthly Growth /
open-issues widgets never appear, even though the BFF api/mainpage/index.php returns
correct data.

Root cause chain
gui/templates/i18n/i18n.js:129-137βfunction t(key, params)treatsparamsas an interpolation bucket and iterates it with jQuery$.each.- jQuery's
$.each/isArrayLikeevaluates'length' in obj. Passing a plain string asparamsmakes thatincheck throw:TypeError: Cannot use 'in' operator to search for 'length' in "Generated on"β verified in isolation (node:'length' in "Generated on"reproduces the exact message). - The throw happens while
render()draws the widgets, so the whole widget pass aborts.
Why it no longer manifests on the default branch: the modern Dashboard rewrite
(57378a549 β ec804b086 "review fixes" β 1274af79c, Refs #780) uses single-argument
t('dash.generatedOn') / t('dash.noRecords') etc. throughout, with the English literals
supplied by the locale bundles. grep across all gui/templates/**/*.html shows zero
remaining two-argument string t() calls, so the primary cause is already absent from the
current source. The issue stayed open because the underlying fragility of t() was never
removed.
Blast radius: the failure lived in the shared t(key, params) contract used by every
modernized screen (cfieldsView.html, etc.), so a future two-arg call could reintroduce the
crash on any screen. Current calling sites = 0.
Approach β harden the shared t() contract (defense-in-depth)
The minimal, safe fix is a type guard inside TLi18n.t() (gui/templates/i18n/i18n.js):
function t(key, params) {
var str = _strings[key] || key;
if (params && typeof params === 'object') {
$.each(params, function(k, v) {
str = str.replace(new RegExp('\\{' + k + '\\}', 'g'), v);
});
}
return str;
}
Interpolation now only runs for a typeof 'object' value (the documented {k: v} bucket, or
an [{0: v}] array used by resultsTCFlat.html/absoluteLatest.html). A stray plain-string
second argument is ignored instead of being fed to $.each, so the whole class of
Β«Cannot use 'in' operatorΒ» crash cannot recur on any modernized screen.
Alternatives rejected:
- Removing the second parameter from the
t()signature entirely: a breaking API change with no benefit β object interpolation (t('key', {n:5})) is a legit, documented use (i18n.js:14-18) and must keep working. - Only fixing
mainPage.html: the reported file already had no two-arg calls, so this alone would have done nothing and left every other screen vulnerable to the same latent crash. - An initial
&& !Array.isArray(params)guard (review finding): it regressed the two array-interpolation callers (rtf.rowCountinresultsTCFlat.html,alx.rowCountinabsoluteLatest.html).typeof 'object'alone is sufficient β it still blocks the string bug case while keeping arrays working.
Files changed
| File | Change |
|---|---|
gui/templates/i18n/i18n.js |
t() gains a typeof params === 'object' guard before $.each (defense-in-depth) |
tmp/TLU_Test_Cases.md |
+Suite 781 (dashboard i18n regression, PASS) |
tmp/fixtures_781.php |
fixture β project DASH781, plan Plan781, build B781, 5 executions (3 passed/1 failed/1 blocked) |
Verification
node --check gui/templates/i18n/i18n.jsβ no syntax errors.- Primary:
tmp/fixtures_781.phpβ open the modern Dashboard for the DASH781 fixture: execution table populated (Passed 3/60%, Failed 1/20%, Blocked 1/20%, Not Run 0/0%, Total 5), Monthly Growth widget visible, browser console 0 errors. - In-page checks (
evaluate_script):TLi18n.t('dash.generatedOn','Generated on')β"Generated on", no throw (pre-fix class).TLi18n.t('dash.testPlan')(single-arg) β"Test Plan", no throw.TLi18n.t('dash.completed',{n:5})(object interpolation) β"Completed", no throw.TLi18n.t('rtf.rowCount',[5])(array interpolation) β"5 rows", no throw.
eventstable:SELECT COUNT(*) FROM events WHERE log_level IN (4,8)β 0 (no new Error/Warning).