Passed
Push — master ( cb41c7...97cf22 )
by Jesús
01:56
created

language.ts ➔ updateBasicUITranslations   A

Complexity

Conditions 1

Size

Total Lines 11
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 11
rs 9.85
c 0
b 0
f 0
cc 1
1
import { getTranslation, wordlistToUILang, type Translations } from '../i18n';
2
import { elements } from '../core/dom';
3
import { loadWordlist } from './wordlist';
4
import { updateDisplay } from '../components/display';
5
6
export let currentTranslations: Translations = getTranslation('en');
7
export let currentLanguage = 'english';
8
9
const languageFlagsSVG: Record<string, string> = {
10
  'english': `<svg class="flag-icon" width="28" height="28"><use href="/sprite.svg#flag-uk"/></svg>`,
11
  'spanish': `<svg class="flag-icon" width="28" height="28"><use href="/sprite.svg#flag-es"/></svg>`,
12
  'french': `<svg class="flag-icon" width="28" height="28"><use href="/sprite.svg#flag-fr"/></svg>`,
13
  'italian': `<svg class="flag-icon" width="28" height="28"><use href="/sprite.svg#flag-it"/></svg>`,
14
  'portuguese': `<svg class="flag-icon" width="28" height="28"><use href="/sprite.svg#flag-pt"/></svg>`,
15
  'czech': `<svg class="flag-icon" width="28" height="28"><use href="/sprite.svg#flag-cz"/></svg>`,
16
  'japanese': `<svg class="flag-icon" width="28" height="28"><use href="/sprite.svg#flag-jp"/></svg>`,
17
  'korean': `<svg class="flag-icon" width="28" height="28"><use href="/sprite.svg#flag-kr"/></svg>`,
18
  'chinese_simplified': `<svg class="flag-icon" width="28" height="28"><use href="/sprite.svg#flag-cn"/></svg>`,
19
  'chinese_traditional': `<svg class="flag-icon" width="28" height="28"><use href="/sprite.svg#flag-tw"/></svg>`,
20
};
21
22
const browserLangToWordlist: Record<string, string> = {
23
  'en': 'english',
24
  'es': 'spanish',
25
  'fr': 'french',
26
  'it': 'italian',
27
  'pt': 'portuguese',
28
  'cs': 'czech',
29
  'ja': 'japanese',
30
  'ko': 'korean',
31
  'zh-Hans': 'chinese_simplified',
32
  'zh-CN': 'chinese_simplified',
33
  'zh-SG': 'chinese_simplified',
34
  'zh-Hant': 'chinese_traditional',
35
  'zh-TW': 'chinese_traditional',
36
  'zh-HK': 'chinese_traditional',
37
  'zh-MO': 'chinese_traditional',
38
  'zh': 'chinese_simplified',
39
};
40
41
function getBrowserLanguage(): string {
42
  if (typeof navigator === 'undefined') {
43
    return 'english';
44
  }
45
46
  // Get browser language (e.g., "en-US", "es", "zh-CN")
47
  const browserLang = navigator.language || (navigator as any).userLanguage;
48
49
  if (!browserLang) {
50
    return 'english';
51
  }
52
53
  // Try exact match first (e.g., "zh-CN")
54
  if (browserLangToWordlist[browserLang]) {
55
    return browserLangToWordlist[browserLang];
56
  }
57
58
  // Try language code only (e.g., "en" from "en-US")
59
  const langCode = browserLang.split('-')[0];
60
  if (browserLangToWordlist[langCode]) {
61
    return browserLangToWordlist[langCode];
62
  }
63
64
  return 'english';
65
}
66
67
export function initLanguage(): string {
68
  const savedLanguage = localStorage.getItem('language');
69
  const defaultLanguage = savedLanguage || getBrowserLanguage();
70
  currentLanguage = defaultLanguage;
71
72
  elements.currentFlag.innerHTML = languageFlagsSVG[defaultLanguage] || languageFlagsSVG['english'];
73
74
  updateActiveLanguageOption();
75
  
76
  return defaultLanguage;
77
}
78
79
export async function changeLanguage(newLanguage: string): Promise<void> {
80
  currentLanguage = newLanguage;
81
  localStorage.setItem('language', newLanguage);
82
  
83
  elements.currentFlag.innerHTML = languageFlagsSVG[newLanguage] || languageFlagsSVG['english'];
84
  
85
  const uiLang = wordlistToUILang[newLanguage] || 'en';
86
  currentTranslations = getTranslation(uiLang);
87
  
88
  updateActiveLanguageOption();
89
  
90
  await loadWordlist(newLanguage);
91
  updateUITranslations();
92
}
93
94
function updateActiveLanguageOption(): void {
95
  const options = elements.languageDropdown.querySelectorAll('.language-option');
96
  options.forEach(option => {
97
    const btn = option as HTMLButtonElement;
98
    if (btn.dataset.lang === currentLanguage) {
99
      btn.classList.add('active');
100
    } else {
101
      btn.classList.remove('active');
102
    }
103
  });
104
}
105
106
export function setupLanguageToggle(): void {
107
  let isOpen = false;
108
109
  // Toggle dropdown
110
  elements.languageToggle.addEventListener('click', (e) => {
111
    e.stopPropagation();
112
    isOpen = !isOpen;
113
    elements.languageDropdown.classList.toggle('open', isOpen);
114
    elements.languageToggle.setAttribute('aria-expanded', isOpen.toString());
115
  });
116
117
  // Close dropdown when clicking outside
118
  document.addEventListener('click', (e) => {
119
    if (isOpen && !elements.languageDropdown.contains(e.target as Node)) {
120
      isOpen = false;
121
      elements.languageDropdown.classList.remove('open');
122
      elements.languageToggle.setAttribute('aria-expanded', 'false');
123
    }
124
  });
125
126
  // Handle language option clicks
127
  const options = elements.languageDropdown.querySelectorAll('.language-option');
128
  options.forEach(option => {
129
    option.addEventListener('click', async () => {
130
      const btn = option as HTMLButtonElement;
131
      const lang = btn.dataset.lang;
132
      if (lang) {
133
        await changeLanguage(lang);
134
        // Close dropdown
135
        isOpen = false;
136
        elements.languageDropdown.classList.remove('open');
137
        elements.languageToggle.setAttribute('aria-expanded', 'false');
138
      }
139
    });
140
  });
141
142
  elements.languageToggle.addEventListener('keydown', (e) => {
143
    if (e.key === 'Escape' && isOpen) {
144
      isOpen = false;
145
      elements.languageDropdown.classList.remove('open');
146
      elements.languageToggle.setAttribute('aria-expanded', 'false');
147
      elements.languageToggle.focus();
148
    }
149
  });
150
}
151
152
export function setTranslations(translations: Translations): void {
153
  currentTranslations = translations;
154
}
155
156
function updateBasicUITranslations(): void {
157
  elements.title.textContent = currentTranslations.title;
158
  elements.indexLabel.textContent = currentTranslations.index;
159
  elements.resetButton.textContent = currentTranslations.resetButton;
160
  elements.infoText.textContent = currentTranslations.infoText;
161
  elements.privacyTitle.textContent = currentTranslations.privacyTitle;
162
  elements.privacyText.textContent = currentTranslations.privacyTooltip;
163
  elements.themeToggle.title = currentTranslations.toggleTheme;
164
  elements.languageToggle.title = currentTranslations.languageLabel;
165
  elements.helpIconTitle.textContent = currentTranslations.helpIconLabel;
166
}
167
168
function updateWordInputTranslations(): void {
169
  elements.wordInputLabel.textContent = currentTranslations.wordInputLabel;
170
  elements.wordInput.placeholder = currentTranslations.wordInputPlaceholder;
171
}
172
173
function updateModalTranslations(): void {
174
  elements.modalTitle.textContent = currentTranslations.modalTitle;
175
  
176
  updateModalStep1Translations();
177
  updateModalStep2Translations();
178
  updateModalStep3Translations();
179
  updateModalStep4Translations();
180
  updateModalWarningTranslations();
181
  updateModalWhyTranslations();
182
}
183
184
function updateModalStep1Translations(): void {
185
  elements.modalStep1Title.textContent = currentTranslations.modalStep1Title;
186
  elements.modalStep1Text.textContent = currentTranslations.modalStep1Text;
187
  
188
  elements.modalStep1WordGrid.innerHTML = '';
189
  currentTranslations.modalStep1Words.forEach(word => {
190
    const wordSpan = document.createElement('span');
191
    wordSpan.className = 'word-example';
192
    wordSpan.textContent = word;
193
    elements.modalStep1WordGrid.appendChild(wordSpan);
194
  });
195
}
196
197
function updateModalStep2Translations(): void {
198
  elements.modalStep2Title.textContent = currentTranslations.modalStep2Title;
199
  elements.modalStep2Text.textContent = currentTranslations.modalStep2Text;
200
  elements.modalStep2Word1.textContent = currentTranslations.modalStep1Words[0];
201
  elements.modalStep2Word2.textContent = currentTranslations.modalStep1Words[1];
202
  elements.modalStep2Entropy.textContent = currentTranslations.modalStep2Entropy;
203
}
204
205
function updateModalStep3Translations(): void {
206
  elements.modalStep3Title.textContent = currentTranslations.modalStep3Title;
207
  elements.modalStep3Text.textContent = currentTranslations.modalStep3Text;
208
  elements.modalStep3MasterSeed.textContent = currentTranslations.modalStep3MasterSeed;
209
  elements.modalStep3BitValue.textContent = currentTranslations.modalStep3BitValue;
210
}
211
212
function updateModalStep4Translations(): void {
213
  elements.modalStep4Title.textContent = currentTranslations.modalStep4Title;
214
  elements.modalStep4Text.textContent = currentTranslations.modalStep4Text;
215
  elements.modalStep4PrivateKey.textContent = currentTranslations.modalStep4PrivateKey;
216
  elements.modalStep4PrivateKey1.textContent = currentTranslations.modalStep4PrivateKey1;
217
  elements.modalStep4PrivateKey2.textContent = currentTranslations.modalStep4PrivateKey2;
218
  elements.modalStep4PrivateKey3.textContent = currentTranslations.modalStep4PrivateKey3;
219
  elements.modalStep4BitSize1.textContent = currentTranslations.modalStep4BitSize;
220
  elements.modalStep4BitSize2.textContent = currentTranslations.modalStep4BitSize;
221
  elements.modalStep4BitSize3.textContent = currentTranslations.modalStep4BitSize;
222
  elements.modalStep4PublicKey.textContent = currentTranslations.modalStep4PublicKey;
223
  elements.modalStep4Address.textContent = currentTranslations.modalStep4Address;
224
}
225
226
function updateModalWarningTranslations(): void {
227
  elements.modalWarningTitle.textContent = currentTranslations.modalWarningTitle;
228
  elements.modalWarningText.textContent = currentTranslations.modalWarningText;
229
  elements.modalWarningItem1.textContent = currentTranslations.modalWarningItem1;
230
  elements.modalWarningItem2.textContent = currentTranslations.modalWarningItem2;
231
  elements.modalWarningItem3.textContent = currentTranslations.modalWarningItem3;
232
  elements.modalWarningItem4.textContent = currentTranslations.modalWarningItem4;
233
}
234
235
function updateModalWhyTranslations(): void {
236
  elements.modalWhyTitle.textContent = currentTranslations.modalWhyBIP39Title;
237
  elements.modalWhyText.textContent = currentTranslations.modalWhyBIP39Text;
238
  
239
  elements.modalWhyLink.innerHTML = `
240
    <svg width="18" height="18" style="display: inline-block; vertical-align: -0.1rem; margin-right: 0.5rem;">
241
      <use href="/sprite.svg#icon-lightbulb"/>
242
    </svg>
243
    ${currentTranslations.modalWhyBIP39Link}
244
  `;
245
}
246
247
export function updateUITranslations(): void {
248
  updateBasicUITranslations();
249
  updateWordInputTranslations();
250
  updateModalTranslations();
251
  
252
  updateDisplay();
253
}
254