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 += ``;
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',