Passed
Push — master ( 5f67fe...8bce0b )
by Jesús
02:16
created

src/modules/language/infrastructure/language.ts   A

Complexity

Total Complexity 24
Complexity/F 1.5

Size

Lines of Code 255
Function Count 16

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 207
dl 0
loc 255
rs 10
c 0
b 0
f 0
wmc 24
mnd 8
bc 8
fnc 16
bpm 0.5
cpm 1.5
noi 0

16 Functions

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