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.
This commit is contained in:
Michael Lip authored and GitHub committed 2026-10-08 11:42:07 -07:00
1 parent 80736de0f1
commit 4ea93e6a73
3 files changed
+31 -2

No files matched your search

+2 -2
View File
@@ -568,7 +568,7 @@ async function UIWindowLogin (options) {
}
h2fa += '</div>';
h2fa += '<div class="login-2fa-error"></div>';
h2fa += `<div class="login-2fa-spinner"><div class="login-2fa-spinner-icon"></div><span>${i18n('verifying') || 'Verifying...'}</span></div>`;
h2fa += `<div class="login-2fa-spinner"><div class="login-2fa-spinner-icon"></div><span>${i18n('verifying')}</span></div>`;
h2fa += `<button type="button" class="login-2fa-link-btn login-2fa-to-recovery">${i18n('login2fa_use_recovery_code')}</button>`;
h2fa += '</div>';
@@ -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;
}
+26
View File
@@ -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([]);
});
});
+3
View File
@@ -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',