From 4ea93e6a73524bb8aa36dcb2e736d640d86b6c23 Mon Sep 17 00:00:00 2001
From: Michael Lip <51033404+theluckystrike@users.noreply.github.com>
Date: Fri, 9 Oct 2026 01:42:07 +0700
Subject: [PATCH] fix(i18n): add GUI keys that rendered as raw key names
(#4074)
The 2FA spinner showed "verifying", the files selection bar showed a
"done" button, and two login and signup errors showed their key names,
because i18n() returns the key itself when en.js has no entry for it.
The login error now uses the existing login_email_username_required key,
which already has translations. A test checks that every literal i18n()
key in the GUI exists in en.js.
---
src/gui/src/UI/UIWindowLogin.js | 4 ++--
src/gui/src/i18n/i18nKeys.test.js | 26 ++++++++++++++++++++++++++
src/gui/src/i18n/translations/en.js | 3 +++
3 files changed, 31 insertions(+), 2 deletions(-)
create mode 100644 src/gui/src/i18n/i18nKeys.test.js
diff --git a/src/gui/src/UI/UIWindowLogin.js b/src/gui/src/UI/UIWindowLogin.js
index 48d5b63e1..170d1f3f5 100644
--- a/src/gui/src/UI/UIWindowLogin.js
+++ b/src/gui/src/UI/UIWindowLogin.js
@@ -568,7 +568,7 @@ async function UIWindowLogin (options) {
}
h2fa += '';
h2fa += '
';
- h2fa += `
${i18n('verifying') || 'Verifying...'}
`;
+ h2fa += `
${i18n('verifying')}
`;
h2fa += ``;
h2fa += '';
@@ -859,7 +859,7 @@ async function UIWindowLogin (options) {
// Basic validation
if ( ! email_username ) {
- $(el_window).find('.login-error-msg').html(i18n('email_or_username_required') || 'Email or username is required');
+ $(el_window).find('.login-error-msg').html(i18n('login_email_username_required'));
$(el_window).find('.login-error-msg').fadeIn();
return false;
}
diff --git a/src/gui/src/i18n/i18nKeys.test.js b/src/gui/src/i18n/i18nKeys.test.js
new file mode 100644
index 000000000..2a41315e0
--- /dev/null
+++ b/src/gui/src/i18n/i18nKeys.test.js
@@ -0,0 +1,26 @@
+import { readdirSync, readFileSync } from 'node:fs';
+import { dirname, join, relative } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { describe, expect, it } from 'vitest';
+import en from './translations/en.js';
+
+const guiSrc = join(dirname(fileURLToPath(import.meta.url)), '..');
+
+const sourceFiles = dir => readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
+ const path = join(dir, entry.name);
+ if ( entry.isDirectory() ) return sourceFiles(path);
+ return path.endsWith('.js') && !path.endsWith('.test.js') ? [path] : [];
+});
+
+describe('i18n keys', () => {
+ // A key missing from en.js renders as the raw key, in every locale.
+ it('every literal i18n() key in the GUI exists in en.js', () => {
+ const missing = [];
+ for ( const file of sourceFiles(guiSrc) ) {
+ for ( const [, key] of readFileSync(file, 'utf8').matchAll(/\bi18n\(\s*['"]([\w.-]+)['"]/g) ) {
+ if ( ! Object.hasOwn(en.dictionary, key) ) missing.push(`${relative(guiSrc, file)}: ${key}`);
+ }
+ }
+ expect(missing).toEqual([]);
+ });
+});
diff --git a/src/gui/src/i18n/translations/en.js b/src/gui/src/i18n/translations/en.js
index 00d73efd5..b27bf6e8b 100644
--- a/src/gui/src/i18n/translations/en.js
+++ b/src/gui/src/i18n/translations/en.js
@@ -108,6 +108,7 @@ const en = {
confirm_delete_multiple_items: 'Are you sure you want to permanently delete these items?',
confirm_delete_single_item: 'Do you want to permanently delete this item?',
confirm_open_apps_log_out: 'You have open apps. Are you sure you want to log out?',
+ confirm_password_required: 'Please confirm your password.',
confirm_new_password: 'Confirm New Password',
confirm_delete_user: 'Are you sure you want to delete your account? All your files and data will be permanently deleted. This action cannot be undone.',
confirm_delete_user_title: 'Delete Account?',
@@ -169,6 +170,7 @@ const en = {
disassociate_dir: 'Disassociate Directory',
disk_storage: 'Disk Storage',
documents: 'Documents',
+ done: 'Done',
dont_allow: 'Don\'t Allow',
download: 'Download',
confirm_download_file_to_desktop: 'Are you sure you want to download %% to your Desktop?',
@@ -755,6 +757,7 @@ const en = {
username: 'Username',
username_changed: 'Username updated successfully.',
username_required: 'Username is required.',
+ verifying: 'Verifying...',
versions: 'Versions',
videos: 'Videos',
visibility: 'Visibility',