3v324v23 commited on
Commit
b7ca495
·
1 Parent(s): d5840a0

implement language dropdown and update language selection functionality

Browse files
Files changed (5) hide show
  1. index.html +29 -1
  2. login.html +29 -1
  3. login.js +77 -2
  4. main.js +70 -1
  5. styles.css +123 -2
index.html CHANGED
@@ -21,7 +21,35 @@
21
  <div class="header-actions">
22
  <div class="language-switcher">
23
  <label class="language-label" for="language-select" data-i18n="languageLabel">Language</label>
24
- <select id="language-select" class="language-select">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  <option value="ja" data-i18n="languageJapanese">Japanese</option>
26
  <option value="en" data-i18n="languageEnglish" selected>English</option>
27
  <option value="vi" data-i18n="languageVietnamese">Vietnamese</option>
 
21
  <div class="header-actions">
22
  <div class="language-switcher">
23
  <label class="language-label" for="language-select" data-i18n="languageLabel">Language</label>
24
+ <div id="language-dropdown" class="language-dropdown">
25
+ <button
26
+ id="language-toggle"
27
+ type="button"
28
+ class="language-toggle"
29
+ aria-haspopup="listbox"
30
+ aria-expanded="false"
31
+ >
32
+ <span id="language-selected-label">English</span>
33
+ <span class="language-chevron" aria-hidden="true"></span>
34
+ </button>
35
+ <ul
36
+ id="language-options"
37
+ class="language-options"
38
+ role="listbox"
39
+ aria-labelledby="language-toggle"
40
+ >
41
+ <li class="language-option" data-lang="ja" role="option">
42
+ <span data-i18n="languageJapanese">Japanese</span>
43
+ </li>
44
+ <li class="language-option" data-lang="en" role="option">
45
+ <span data-i18n="languageEnglish">English</span>
46
+ </li>
47
+ <li class="language-option" data-lang="vi" role="option">
48
+ <span data-i18n="languageVietnamese">Vietnamese</span>
49
+ </li>
50
+ </ul>
51
+ </div>
52
+ <select id="language-select" class="language-select sr-only">
53
  <option value="ja" data-i18n="languageJapanese">Japanese</option>
54
  <option value="en" data-i18n="languageEnglish" selected>English</option>
55
  <option value="vi" data-i18n="languageVietnamese">Vietnamese</option>
login.html CHANGED
@@ -10,7 +10,35 @@
10
  <div class="login-layout">
11
  <div class="login-language-switcher">
12
  <label class="language-label" for="language-select" data-i18n="languageLabel">Language</label>
13
- <select id="language-select" class="language-select">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  <option value="ja" data-i18n="languageJapanese">Japanese</option>
15
  <option value="en" data-i18n="languageEnglish" selected>English</option>
16
  <option value="vi" data-i18n="languageVietnamese">Vietnamese</option>
 
10
  <div class="login-layout">
11
  <div class="login-language-switcher">
12
  <label class="language-label" for="language-select" data-i18n="languageLabel">Language</label>
13
+ <div id="language-dropdown" class="language-dropdown">
14
+ <button
15
+ id="language-toggle"
16
+ type="button"
17
+ class="language-toggle"
18
+ aria-haspopup="listbox"
19
+ aria-expanded="false"
20
+ >
21
+ <span id="language-selected-label">English</span>
22
+ <span class="language-chevron" aria-hidden="true"></span>
23
+ </button>
24
+ <ul
25
+ id="language-options"
26
+ class="language-options"
27
+ role="listbox"
28
+ aria-labelledby="language-toggle"
29
+ >
30
+ <li class="language-option" data-lang="ja" role="option">
31
+ <span data-i18n="languageJapanese">Japanese</span>
32
+ </li>
33
+ <li class="language-option" data-lang="en" role="option">
34
+ <span data-i18n="languageEnglish">English</span>
35
+ </li>
36
+ <li class="language-option" data-lang="vi" role="option">
37
+ <span data-i18n="languageVietnamese">Vietnamese</span>
38
+ </li>
39
+ </ul>
40
+ </div>
41
+ <select id="language-select" class="language-select sr-only">
42
  <option value="ja" data-i18n="languageJapanese">Japanese</option>
43
  <option value="en" data-i18n="languageEnglish" selected>English</option>
44
  <option value="vi" data-i18n="languageVietnamese">Vietnamese</option>
login.js CHANGED
@@ -1,4 +1,11 @@
1
- import { initI18n, bindLanguageSelector, t, onLanguageChange } from './i18n.js';
 
 
 
 
 
 
 
2
  import {
3
  findAccount,
4
  getAuthUser,
@@ -14,6 +21,73 @@ const errorElement = document.getElementById('login-error');
14
  initI18n();
15
  bindLanguageSelector('#language-select');
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  function redirectToApp() {
18
  window.location.href = 'index.html';
19
  }
@@ -33,7 +107,8 @@ if (existingUser) {
33
 
34
  applyLoginTitle();
35
 
36
- onLanguageChange(() => {
 
37
  applyLoginTitle();
38
  if (errorElement.textContent) {
39
  setError(t('loginError'));
 
1
+ import {
2
+ initI18n,
3
+ bindLanguageSelector,
4
+ t,
5
+ onLanguageChange,
6
+ setLanguage,
7
+ getCurrentLanguage,
8
+ } from './i18n.js';
9
  import {
10
  findAccount,
11
  getAuthUser,
 
21
  initI18n();
22
  bindLanguageSelector('#language-select');
23
 
24
+ const languageLabelMap = {
25
+ en: () => t('languageEnglish') || 'English',
26
+ ja: () => t('languageJapanese') || 'Japanese',
27
+ vi: () => t('languageVietnamese') || 'Vietnamese',
28
+ };
29
+
30
+ const languageDropdown = document.getElementById('language-dropdown');
31
+ const languageToggle = document.getElementById('language-toggle');
32
+ const languageOptions = document.querySelectorAll('.language-option');
33
+ const languageSelectedLabel = document.getElementById(
34
+ 'language-selected-label'
35
+ );
36
+ const hiddenLanguageSelect = document.getElementById('language-select');
37
+
38
+ function setLanguageUI(lang) {
39
+ const labelFn = languageLabelMap[lang];
40
+ if (languageSelectedLabel && labelFn) {
41
+ languageSelectedLabel.textContent = labelFn();
42
+ }
43
+ languageOptions.forEach((option) => {
44
+ const isActive = option.dataset.lang === lang;
45
+ option.classList.toggle('active', isActive);
46
+ option.setAttribute('aria-selected', isActive.toString());
47
+ });
48
+ if (hiddenLanguageSelect) {
49
+ hiddenLanguageSelect.value = lang;
50
+ }
51
+ if (languageToggle) {
52
+ languageToggle.setAttribute('aria-expanded', 'false');
53
+ }
54
+ if (languageDropdown) {
55
+ languageDropdown.classList.remove('open');
56
+ }
57
+ }
58
+
59
+ function setupLanguageDropdown() {
60
+ if (!languageDropdown || !languageToggle) return;
61
+
62
+ languageToggle.addEventListener('click', (event) => {
63
+ event.stopPropagation();
64
+ const isOpen = languageDropdown.classList.toggle('open');
65
+ languageToggle.setAttribute('aria-expanded', isOpen.toString());
66
+ });
67
+
68
+ languageOptions.forEach((option) => {
69
+ option.addEventListener('click', (event) => {
70
+ event.stopPropagation();
71
+ const lang = option.dataset.lang;
72
+ if (lang) {
73
+ setLanguage(lang);
74
+ }
75
+ });
76
+ });
77
+
78
+ document.addEventListener('click', (event) => {
79
+ if (!languageDropdown.classList.contains('open')) return;
80
+ if (!languageDropdown.contains(event.target)) {
81
+ languageDropdown.classList.remove('open');
82
+ languageToggle.setAttribute('aria-expanded', 'false');
83
+ }
84
+ });
85
+
86
+ setLanguageUI(getCurrentLanguage());
87
+ }
88
+
89
+ setupLanguageDropdown();
90
+
91
  function redirectToApp() {
92
  window.location.href = 'index.html';
93
  }
 
107
 
108
  applyLoginTitle();
109
 
110
+ onLanguageChange((lang) => {
111
+ setLanguageUI(lang);
112
  applyLoginTitle();
113
  if (errorElement.textContent) {
114
  setError(t('loginError'));
main.js CHANGED
@@ -4,6 +4,7 @@ import {
4
  bindLanguageSelector,
5
  onLanguageChange,
6
  getCurrentLanguage,
 
7
  } from './i18n.js';
8
  import { closeIcon, eyeIcon, trashIcon } from './constants.js';
9
  import {
@@ -59,6 +60,73 @@ const languageApiMap = {
59
  vi: 'Vietnamese',
60
  };
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  const formInputs = [
63
  textInputElement,
64
  socialMediaInputElement,
@@ -82,7 +150,8 @@ function updateSubmitButtonLabel() {
82
  : t('submitButton');
83
  }
84
 
85
- onLanguageChange(() => {
 
86
  updateSubmitButtonLabel();
87
  if (!data.value) {
88
  setDefaultOutputPlaceholder();
 
4
  bindLanguageSelector,
5
  onLanguageChange,
6
  getCurrentLanguage,
7
+ setLanguage,
8
  } from './i18n.js';
9
  import { closeIcon, eyeIcon, trashIcon } from './constants.js';
10
  import {
 
60
  vi: 'Vietnamese',
61
  };
62
 
63
+ const languageLabelMap = {
64
+ en: () => t('languageEnglish') || 'English',
65
+ ja: () => t('languageJapanese') || 'Japanese',
66
+ vi: () => t('languageVietnamese') || 'Vietnamese',
67
+ };
68
+
69
+ const languageDropdown = document.getElementById('language-dropdown');
70
+ const languageToggle = document.getElementById('language-toggle');
71
+ const languageOptions = document.querySelectorAll('.language-option');
72
+ const languageSelectedLabel = document.getElementById(
73
+ 'language-selected-label'
74
+ );
75
+ const hiddenLanguageSelect = document.getElementById('language-select');
76
+
77
+ function setLanguageUI(lang) {
78
+ const labelFn = languageLabelMap[lang];
79
+ if (languageSelectedLabel && labelFn) {
80
+ languageSelectedLabel.textContent = labelFn();
81
+ }
82
+ languageOptions.forEach((option) => {
83
+ const isActive = option.dataset.lang === lang;
84
+ option.classList.toggle('active', isActive);
85
+ option.setAttribute('aria-selected', isActive.toString());
86
+ });
87
+ if (hiddenLanguageSelect) {
88
+ hiddenLanguageSelect.value = lang;
89
+ }
90
+ if (languageToggle) {
91
+ languageToggle.setAttribute('aria-expanded', 'false');
92
+ }
93
+ if (languageDropdown) {
94
+ languageDropdown.classList.remove('open');
95
+ }
96
+ }
97
+
98
+ function setupLanguageDropdown() {
99
+ if (!languageDropdown || !languageToggle) return;
100
+
101
+ languageToggle.addEventListener('click', (event) => {
102
+ event.stopPropagation();
103
+ const isOpen = languageDropdown.classList.toggle('open');
104
+ languageToggle.setAttribute('aria-expanded', isOpen.toString());
105
+ });
106
+
107
+ languageOptions.forEach((option) => {
108
+ option.addEventListener('click', (event) => {
109
+ event.stopPropagation();
110
+ const lang = option.dataset.lang;
111
+ if (lang) {
112
+ setLanguage(lang);
113
+ }
114
+ });
115
+ });
116
+
117
+ document.addEventListener('click', (event) => {
118
+ if (!languageDropdown.classList.contains('open')) return;
119
+ if (!languageDropdown.contains(event.target)) {
120
+ languageDropdown.classList.remove('open');
121
+ languageToggle.setAttribute('aria-expanded', 'false');
122
+ }
123
+ });
124
+
125
+ setLanguageUI(getCurrentLanguage());
126
+ }
127
+
128
+ setupLanguageDropdown();
129
+
130
  const formInputs = [
131
  textInputElement,
132
  socialMediaInputElement,
 
150
  : t('submitButton');
151
  }
152
 
153
+ onLanguageChange((lang) => {
154
+ setLanguageUI(lang);
155
  updateSubmitButtonLabel();
156
  if (!data.value) {
157
  setDefaultOutputPlaceholder();
styles.css CHANGED
@@ -125,11 +125,132 @@ body {
125
  background-repeat: no-repeat;
126
  background-position: right 12px center;
127
  background-size: 14px;
 
 
 
128
  }
129
 
130
  .language-select:focus {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  outline: 2px solid #fd7e14;
132
- outline-offset: 1px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  }
134
 
135
  /* Main Content Layout */
@@ -1195,5 +1316,5 @@ textarea:focus {
1195
 
1196
  .btn-logout {
1197
  padding: 6px 10px;
1198
- height: 37px;
1199
  }
 
125
  background-repeat: no-repeat;
126
  background-position: right 12px center;
127
  background-size: 14px;
128
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
129
+ transition: border-color 0.2s ease, background-color 0.2s ease,
130
+ box-shadow 0.2s ease;
131
  }
132
 
133
  .language-select:focus {
134
+ border-color: #fd7e14;
135
+ outline: none;
136
+ }
137
+
138
+ .language-select:hover {
139
+ border-color: #b6bec8;
140
+ background-color: #fdfdfd;
141
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
142
+ }
143
+
144
+ .language-select option {
145
+ padding: 8px 10px;
146
+ color: #1f2937;
147
+ background-color: #ffffff;
148
+ font-weight: 500;
149
+ }
150
+
151
+ .language-select option:checked {
152
+ background-color: #fff4e6;
153
+ color: #d35400;
154
+ }
155
+
156
+ .sr-only {
157
+ position: absolute;
158
+ width: 1px;
159
+ height: 1px;
160
+ padding: 0;
161
+ margin: -1px;
162
+ overflow: hidden;
163
+ clip: rect(0, 0, 0, 0);
164
+ white-space: nowrap;
165
+ border: 0;
166
+ }
167
+
168
+ .language-dropdown {
169
+ position: relative;
170
+ min-width: 170px;
171
+ }
172
+
173
+ .language-toggle {
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: space-between;
177
+ gap: 10px;
178
+ width: 100%;
179
+ padding: 6px 12px;
180
+ background: #ffffff;
181
+ border: 1px solid #ced4da;
182
+ border-radius: 8px;
183
+ color: #1f2937;
184
+ font-weight: 600;
185
+ cursor: pointer;
186
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
187
+ transition: border-color 0.2s ease, box-shadow 0.2s ease,
188
+ transform 0.15s ease;
189
+ }
190
+
191
+ .language-toggle:hover {
192
+ border-color: #b6bec8;
193
+ box-shadow: 0 6px 14px rgba(0, 0, 0, 0.08);
194
+ transform: translateY(-1px);
195
+ }
196
+
197
+ .language-toggle:focus-visible {
198
  outline: 2px solid #fd7e14;
199
+ outline-offset: 2px;
200
+ }
201
+
202
+ .language-chevron {
203
+ width: 0;
204
+ height: 0;
205
+ border-left: 6px solid transparent;
206
+ border-right: 6px solid transparent;
207
+ border-top: 6px solid #4b5563;
208
+ transition: transform 0.2s ease;
209
+ }
210
+
211
+ .language-dropdown.open .language-chevron {
212
+ transform: rotate(180deg);
213
+ }
214
+
215
+ .language-options {
216
+ position: absolute;
217
+ top: calc(100% + 8px);
218
+ right: 0;
219
+ width: 220px;
220
+ background: #ffffff;
221
+ border: 1px solid #d7dce3;
222
+ border-radius: 10px;
223
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.12);
224
+ padding: 6px;
225
+ display: none;
226
+ flex-direction: column;
227
+ gap: 4px;
228
+ z-index: 20;
229
+ }
230
+
231
+ .language-dropdown.open .language-options {
232
+ display: flex;
233
+ }
234
+
235
+ .language-option {
236
+ padding: 10px 12px;
237
+ border-radius: 8px;
238
+ cursor: pointer;
239
+ font-weight: 600;
240
+ color: #1f2937;
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: space-between;
244
+ transition: background-color 0.15s ease, color 0.15s ease;
245
+ }
246
+
247
+ .language-option:hover {
248
+ background-color: #f5f7fb;
249
+ }
250
+
251
+ .language-option.active {
252
+ background-color: #fff4e6;
253
+ color: #d35400;
254
  }
255
 
256
  /* Main Content Layout */
 
1316
 
1317
  .btn-logout {
1318
  padding: 6px 10px;
1319
+ height: 38px;
1320
  }