Skip to content

Commit 2948705

Browse files
committed
refactor(i18n): streamline initialization and enhance dynamic element handling
- Remove unused RTL language utilities and functions from background.ts - Move populateModelDropdown function outside DOMContentLoaded in options.ts for better organization - Add debug logging for model dropdown population in content.ts - Update updateUILanguage in utils.ts to skip OPTION elements and simplify i18n initialization - Ensure model dropdown repopulates on language changes for proper translations
1 parent e7cb8dc commit 2948705

File tree

4 files changed

+163
-117
lines changed

4 files changed

+163
-117
lines changed

background.ts

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,6 @@ interface I18nOptions {
6161
parameters?: string | (string | number)[] | MessageParameters;
6262
}
6363

64-
// RTL language codes
65-
const RTL_LANGUAGES = new Set([
66-
'ar', // Arabic
67-
'he', // Hebrew
68-
'fa', // Persian/Farsi
69-
'ur', // Urdu
70-
'yi', // Yiddish
71-
'az', // Azerbaijani (when written in Arabic script)
72-
'dv', // Divehi
73-
'ku', // Kurdish (when written in Arabic script)
74-
'ps', // Pashto
75-
'sd', // Sindhi
76-
'ug', // Uyghur
77-
]);
78-
7964
// Language detection fallback chain
8065
const LANGUAGE_FALLBACK_CHAIN = [
8166
'en', // English as ultimate fallback
@@ -557,10 +542,6 @@ async function checkChromeBuiltinSupport(): Promise<boolean> {
557542
return version >= 138 && apiAvailable;
558543
}
559544

560-
function getSupportedLanguages(provider: string): string[] {
561-
return LANGUAGE_SUPPORT[provider] || [];
562-
}
563-
564545
function isLanguageSupported(provider: string, language: string): boolean {
565546
const supportedLanguages = LANGUAGE_SUPPORT[provider];
566547
return supportedLanguages ? supportedLanguages.includes(language) : false;
@@ -631,14 +612,6 @@ function getMessage(messageName: string, options: I18nOptions = {}): string {
631612
* Get the current UI language
632613
* @returns The current UI language code (e.g., 'en', 'es')
633614
*/
634-
function getCurrentLanguage(): string {
635-
try {
636-
return chrome.i18n.getUILanguage();
637-
} catch (error) {
638-
console.warn('Failed to get current UI language:', error);
639-
return 'en'; // Default fallback
640-
}
641-
}
642615

643616
/**
644617
* Detect user's preferred language with fallback chain
@@ -679,10 +652,6 @@ function detectUserLanguage(): string {
679652
* @param languageCode - The language code to check
680653
* @returns True if the language is RTL
681654
*/
682-
function isRTLLanguage(languageCode: string): boolean {
683-
const baseLang = languageCode.split('-')[0]; // Remove region
684-
return RTL_LANGUAGES.has(baseLang);
685-
}
686655

687656
/**
688657
* Store user's language preference

content.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,12 @@ async function checkChromeBuiltinSupport(): Promise<boolean> {
15421542
anthropicApiKey: result.anthropicApiKey || '',
15431543
};
15441544

1545+
console.log('DEBUG: Starting to populate model dropdown options');
1546+
console.log(
1547+
'DEBUG: modelSelect before adding options:',
1548+
modelSelect.children.length,
1549+
'children'
1550+
);
15451551
const availableModels: string[] = [];
15461552

15471553
for (const option of modelOptions) {
@@ -1560,10 +1566,22 @@ async function checkChromeBuiltinSupport(): Promise<boolean> {
15601566
opt.disabled = true;
15611567
opt.style.opacity = '0.5';
15621568
opt.title = getMessage('apiKeyRequired');
1569+
console.log(
1570+
`DEBUG: Added option ${option.value}, total children now: ${modelSelect.children.length}`
1571+
);
15631572
}
15641573

15651574
modelSelect.appendChild(opt);
15661575
}
1576+
console.log(
1577+
'DEBUG: Finished adding options, total children:',
1578+
modelSelect.children.length
1579+
);
1580+
console.log(
1581+
'DEBUG: HTMLOptionsCollection length:',
1582+
modelSelect.options.length
1583+
);
1584+
console.log('DEBUG: Available models:', availableModels);
15671585

15681586
// Load current selected model
15691587
chrome.storage.sync.get('selectedModel', (result) => {
@@ -1771,6 +1789,22 @@ async function checkChromeBuiltinSupport(): Promise<boolean> {
17711789
if (metrics && metrics.attempts && metrics.attempts.length > 1) {
17721790
statusContent += ` • ${metrics.attempts.length} attempts`;
17731791
}
1792+
// Apply language updates to the summary div after creation
1793+
if (summaryDiv) {
1794+
console.log('DEBUG: About to call updateUILanguage on summaryDiv');
1795+
updateUILanguage(summaryDiv, language);
1796+
const modelSelect = document.getElementById(
1797+
'ai-summary-model-selector'
1798+
) as HTMLSelectElement;
1799+
console.log(
1800+
'DEBUG: After updateUILanguage, modelSelect children:',
1801+
modelSelect.children.length
1802+
);
1803+
console.log(
1804+
'DEBUG: After updateUILanguage, HTMLOptionsCollection length:',
1805+
modelSelect.options.length
1806+
);
1807+
}
17741808
statusText.textContent = statusContent;
17751809
// Clear any i18n attribute since this is dynamic content
17761810
statusText.removeAttribute('data-i18n');

options.ts

Lines changed: 116 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,124 @@ function optionsGetModelConfig(model: string): ModelConfig | undefined {
369369
return models[model];
370370
}
371371

372+
// Populate AI Model dropdown dynamically
373+
function populateModelDropdown() {
374+
console.log('populateModelDropdown called');
375+
const selectedModelSelect = document.getElementById(
376+
'selectedModel'
377+
) as HTMLSelectElement;
378+
console.log('selectedModelSelect element:', selectedModelSelect);
379+
console.log(
380+
'selectedModelSelect options:',
381+
selectedModelSelect ? selectedModelSelect.options : 'No element found'
382+
);
383+
console.log(
384+
'selectedModelSelect children:',
385+
selectedModelSelect ? selectedModelSelect.children : 'No element found'
386+
);
387+
console.log(
388+
'selectedModelSelect innerHTML:',
389+
selectedModelSelect ? selectedModelSelect.innerHTML : 'No element found'
390+
);
391+
if (!selectedModelSelect) {
392+
console.log('selectedModelSelect not found');
393+
return;
394+
}
395+
396+
// Clear existing options
397+
selectedModelSelect.innerHTML = '';
398+
console.log('Cleared existing options');
399+
400+
// Define model groups with their configurations
401+
const modelGroups = [
402+
{
403+
labelKey: 'freeModels',
404+
models: [
405+
{ value: 'chrome-builtin', i18nKey: 'chromeAiFree' },
406+
{ value: 'gemini-2.0-flash-exp', i18nKey: 'gemini20FlashExp' },
407+
],
408+
},
409+
{
410+
labelKey: 'openaiModels',
411+
models: [
412+
{ value: 'gpt-3.5-turbo', i18nKey: 'gpt35Turbo' },
413+
{ value: 'gpt-4', i18nKey: 'gpt4' },
414+
{ value: 'gpt-4-turbo', i18nKey: 'gpt4Turbo' },
415+
{ value: 'gpt-4o', i18nKey: 'gpt4o' },
416+
{ value: 'gpt-5', i18nKey: 'gpt5' },
417+
{ value: 'gpt-5-mini', i18nKey: 'gpt5Mini' },
418+
{ value: 'gpt-5-nano', i18nKey: 'gpt5Nano' },
419+
],
420+
},
421+
{
422+
labelKey: 'googleGeminiModels',
423+
models: [
424+
{ value: 'gemini-2.5-pro', i18nKey: 'gemini25Pro' },
425+
{ value: 'gemini-2.5-flash', i18nKey: 'gemini25Flash' },
426+
],
427+
},
428+
{
429+
labelKey: 'anthropicClaudeModels',
430+
models: [
431+
{ value: 'claude-3-haiku', i18nKey: 'claude3Haiku' },
432+
{ value: 'claude-3-sonnet', i18nKey: 'claude3Sonnet' },
433+
{ value: 'claude-3-opus', i18nKey: 'claude3Opus' },
434+
{ value: 'claude-3.5-sonnet', i18nKey: 'claude35Sonnet' },
435+
{ value: 'claude-4.5-sonnet', i18nKey: 'claudeSonnet45' },
436+
{ value: 'claude-4.5-haiku', i18nKey: 'claudeHaiku45' },
437+
],
438+
},
439+
];
440+
441+
console.log('modelGroups defined:', modelGroups);
442+
443+
// Create optgroups and options
444+
modelGroups.forEach((group, groupIndex) => {
445+
console.log(`Processing group ${groupIndex}:`, group);
446+
const optgroup = document.createElement('optgroup');
447+
optgroup.setAttribute('data-i18n', group.labelKey);
448+
optgroup.label = group.labelKey; // Fallback label
449+
console.log(`Created optgroup with label: ${group.labelKey}`);
450+
451+
group.models.forEach((model, modelIndex) => {
452+
console.log(
453+
`Processing model ${modelIndex} in group ${groupIndex}:`,
454+
model
455+
);
456+
const option = document.createElement('option');
457+
option.value = model.value;
458+
option.setAttribute('data-i18n', model.i18nKey);
459+
option.textContent = model.i18nKey; // Fallback text
460+
console.log(
461+
`Created option with value: ${model.value}, text: ${model.i18nKey}`
462+
);
463+
optgroup.appendChild(option);
464+
});
465+
466+
console.log(
467+
`Appending optgroup to select, optgroup children count: ${optgroup.children.length}`
468+
);
469+
selectedModelSelect.appendChild(optgroup);
470+
});
471+
472+
console.log(
473+
'Final selectedModelSelect children count:',
474+
selectedModelSelect.children.length
475+
);
476+
console.log(
477+
'Final selectedModelSelect innerHTML:',
478+
selectedModelSelect.innerHTML
479+
);
480+
}
481+
372482
// Initialize i18n inside DOMContentLoaded to ensure DOM elements exist
373483
document.addEventListener('DOMContentLoaded', async function () {
374484
// Initialize i18n after DOM is ready
375485
await initializeI18n();
486+
// Populate model dropdown after i18n is ready
487+
populateModelDropdown();
488+
// Update UI language to translate dynamically added elements
489+
updateUILanguage();
376490
// Theme toggle functionality
377491
const themeToggle = document.getElementById(
378492
'themeToggle'
@@ -444,6 +558,8 @@ document.addEventListener('DOMContentLoaded', async function () {
444558
const selectedLanguage = languageSelect.value;
445559
await setUserLanguage(selectedLanguage);
446560
updateUILanguage(document, selectedLanguage);
561+
// Repopulate model dropdown with new language translations
562+
populateModelDropdown();
447563
});
448564
const temperatureInput = document.getElementById(
449565
'temperature'
@@ -896,74 +1012,6 @@ document.addEventListener('DOMContentLoaded', async function () {
8961012
refreshMetricsButton.addEventListener('click', loadMetrics);
8971013

8981014
// Load and display history
899-
// Populate AI Model dropdown dynamically
900-
function populateModelDropdown() {
901-
const selectedModelSelect = document.getElementById(
902-
'selectedModel'
903-
) as HTMLSelectElement;
904-
if (!selectedModelSelect) return;
905-
906-
// Clear existing options
907-
selectedModelSelect.innerHTML = '';
908-
909-
// Define model groups with their configurations
910-
const modelGroups = [
911-
{
912-
labelKey: 'freeModels',
913-
models: [
914-
{ value: 'chrome-builtin', i18nKey: 'chromeAiFree' },
915-
{ value: 'gemini-2.0-flash-exp', i18nKey: 'gemini20FlashExp' },
916-
],
917-
},
918-
{
919-
labelKey: 'openaiModels',
920-
models: [
921-
{ value: 'gpt-3.5-turbo', i18nKey: 'gpt35Turbo' },
922-
{ value: 'gpt-4', i18nKey: 'gpt4' },
923-
{ value: 'gpt-4-turbo', i18nKey: 'gpt4Turbo' },
924-
{ value: 'gpt-4o', i18nKey: 'gpt4o' },
925-
{ value: 'gpt-5', i18nKey: 'gpt5' },
926-
{ value: 'gpt-5-mini', i18nKey: 'gpt5Mini' },
927-
{ value: 'gpt-5-nano', i18nKey: 'gpt5Nano' },
928-
],
929-
},
930-
{
931-
labelKey: 'googleGeminiModels',
932-
models: [
933-
{ value: 'gemini-2.5-pro', i18nKey: 'gemini25Pro' },
934-
{ value: 'gemini-2.5-flash', i18nKey: 'gemini25Flash' },
935-
],
936-
},
937-
{
938-
labelKey: 'anthropicClaudeModels',
939-
models: [
940-
{ value: 'claude-3-haiku', i18nKey: 'claude3Haiku' },
941-
{ value: 'claude-3-sonnet', i18nKey: 'claude3Sonnet' },
942-
{ value: 'claude-3-opus', i18nKey: 'claude3Opus' },
943-
{ value: 'claude-3.5-sonnet', i18nKey: 'claude35Sonnet' },
944-
{ value: 'claude-4.5-sonnet', i18nKey: 'claudeSonnet45' },
945-
{ value: 'claude-4.5-haiku', i18nKey: 'claudeHaiku45' },
946-
],
947-
},
948-
];
949-
950-
// Create optgroups and options
951-
modelGroups.forEach((group) => {
952-
const optgroup = document.createElement('optgroup');
953-
optgroup.setAttribute('data-i18n', group.labelKey);
954-
optgroup.label = group.labelKey; // Fallback label
955-
956-
group.models.forEach((model) => {
957-
const option = document.createElement('option');
958-
option.value = model.value;
959-
option.setAttribute('data-i18n', model.i18nKey);
960-
option.textContent = model.i18nKey; // Fallback text
961-
optgroup.appendChild(option);
962-
});
963-
964-
selectedModelSelect.appendChild(optgroup);
965-
});
966-
}
9671015
function loadHistory() {
9681016
chrome.storage.local.get('summaryHistory', (result) => {
9691017
displayHistory(result.summaryHistory || []);

utils.ts

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -992,8 +992,10 @@ export function updateUILanguage(
992992
if (messageKey) {
993993
const message = getMessage(messageKey);
994994
if (message !== messageKey) {
995-
// Only update if translation found
996-
element.textContent = message;
995+
// Only update if translation found and skip option and optgroup elements to prevent clearing dynamically added options
996+
if (element.tagName !== 'OPTION' && element.tagName !== 'OPTGROUP') {
997+
element.textContent = message;
998+
}
997999
}
9981000
}
9991001
});
@@ -1049,7 +1051,7 @@ export function updateUILanguage(
10491051
if (messageKey) {
10501052
const message = getMessage(messageKey);
10511053
if (message !== messageKey) {
1052-
element.label = message;
1054+
(element as HTMLOptGroupElement).label = message;
10531055
}
10541056
}
10551057
});
@@ -1122,23 +1124,16 @@ export async function initializeLanguagePreference(): Promise<void> {
11221124
* Should be called when UI contexts load (content script, options page)
11231125
*/
11241126
export async function initializeI18n(): Promise<void> {
1127+
console.log('Initializing i18n system...');
11251128
try {
1126-
// Get stored user language preference
1127-
const storedLanguage = await getUserLanguage();
1129+
// Initialize language preference if not already done
1130+
await initializeLanguagePreference();
11281131

1129-
if (storedLanguage) {
1130-
// Apply stored language preference
1131-
updateUILanguage(document, storedLanguage || undefined);
1132-
console.log(`i18n initialized with stored language: ${storedLanguage}`);
1133-
} else {
1134-
// No stored preference, initialize and use detected language
1135-
await initializeLanguagePreference();
1136-
updateUILanguage();
1137-
console.log(
1138-
`i18n initialized with detected language: ${getCurrentLanguage()}`
1139-
);
1140-
}
1132+
// Update UI with current language
1133+
updateUILanguage();
1134+
1135+
console.log('i18n system initialized successfully');
11411136
} catch (error) {
1142-
console.warn('Failed to initialize i18n:', error);
1137+
console.warn('Failed to initialize i18n system:', error);
11431138
}
11441139
}

0 commit comments

Comments
 (0)