@ait-co/devtools 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/dist/panel/index.d.ts.map +1 -1
- package/dist/panel/index.js +545 -127
- package/dist/panel/index.js.map +1 -1
- package/dist/tunnel-BbcgVy4L.js +114 -0
- package/dist/tunnel-BbcgVy4L.js.map +1 -0
- package/dist/tunnel-DeXfLGRl.cjs +115 -0
- package/dist/tunnel-DeXfLGRl.cjs.map +1 -0
- package/dist/unplugin/index.cjs +34 -0
- package/dist/unplugin/index.cjs.map +1 -1
- package/dist/unplugin/index.d.cts +10 -0
- package/dist/unplugin/index.d.cts.map +1 -1
- package/dist/unplugin/index.d.ts +11 -1
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +34 -0
- package/dist/unplugin/index.js.map +1 -1
- package/dist/unplugin/tunnel.cjs +117 -0
- package/dist/unplugin/tunnel.cjs.map +1 -0
- package/dist/unplugin/tunnel.d.cts +47 -0
- package/dist/unplugin/tunnel.d.cts.map +1 -0
- package/dist/unplugin/tunnel.d.ts +47 -0
- package/dist/unplugin/tunnel.d.ts.map +1 -0
- package/dist/unplugin/tunnel.js +114 -0
- package/dist/unplugin/tunnel.js.map +1 -0
- package/package.json +4 -1
package/dist/panel/index.js
CHANGED
|
@@ -1,3 +1,372 @@
|
|
|
1
|
+
//#region src/i18n/en.ts
|
|
2
|
+
const en = {
|
|
3
|
+
"panel.title": "AIT DevTools",
|
|
4
|
+
"panel.toggle.title": "AIT DevTools",
|
|
5
|
+
"panel.close": "Close",
|
|
6
|
+
"panel.editMode.on": "EDIT",
|
|
7
|
+
"panel.editMode.off": "READ-ONLY",
|
|
8
|
+
"panel.editMode.toggleTitle": "Toggle panel edit mode",
|
|
9
|
+
"panel.tabError": "Error rendering \"{tab}\" tab.",
|
|
10
|
+
"panel.tab.env": "Environment",
|
|
11
|
+
"panel.tab.presets": "Presets",
|
|
12
|
+
"panel.tab.viewport": "Viewport",
|
|
13
|
+
"panel.tab.permissions": "Permissions",
|
|
14
|
+
"panel.tab.notifications": "Notifications",
|
|
15
|
+
"panel.tab.location": "Location",
|
|
16
|
+
"panel.tab.device": "Device",
|
|
17
|
+
"panel.tab.iap": "IAP",
|
|
18
|
+
"panel.tab.ads": "Ads",
|
|
19
|
+
"panel.tab.events": "Events",
|
|
20
|
+
"panel.tab.analytics": "Analytics",
|
|
21
|
+
"panel.tab.storage": "Storage",
|
|
22
|
+
"common.readOnly": "Read-only — mock responses are controlled at build time.",
|
|
23
|
+
"toast.consent.title": "Send anonymous usage stats?",
|
|
24
|
+
"toast.consent.body": "We collect anonymous events only, to improve the tool. You can turn this off anytime in the Environment tab.",
|
|
25
|
+
"toast.consent.learnMore": "Learn more",
|
|
26
|
+
"toast.consent.accept": "Yes, send",
|
|
27
|
+
"toast.consent.deny": "No, thanks",
|
|
28
|
+
"env.section.platform": "Platform",
|
|
29
|
+
"env.row.os": "OS",
|
|
30
|
+
"env.row.appVersion": "App Version",
|
|
31
|
+
"env.row.environment": "Environment",
|
|
32
|
+
"env.row.locale": "Locale",
|
|
33
|
+
"env.section.network": "Network",
|
|
34
|
+
"env.row.networkStatus": "Status",
|
|
35
|
+
"env.section.safeArea": "Safe Area Insets",
|
|
36
|
+
"env.row.safeArea.top": "Top",
|
|
37
|
+
"env.row.safeArea.bottom": "Bottom",
|
|
38
|
+
"env.telemetry.section": "Telemetry",
|
|
39
|
+
"env.telemetry.row": "Telemetry",
|
|
40
|
+
"env.telemetry.on": "On",
|
|
41
|
+
"env.telemetry.off": "Off",
|
|
42
|
+
"env.telemetry.turnOn": "Turn on",
|
|
43
|
+
"env.telemetry.turnOff": "Turn off",
|
|
44
|
+
"env.telemetry.anonIdLabel": "anon_id: {value}",
|
|
45
|
+
"env.telemetry.anonIdNotSet": "(not yet set)",
|
|
46
|
+
"env.telemetry.anonIdCopyTitle": "Click to copy full anon_id",
|
|
47
|
+
"env.telemetry.deleteBtn": "Delete my data",
|
|
48
|
+
"env.telemetry.deleting": "Deleting…",
|
|
49
|
+
"env.telemetry.deleted": "Deleted",
|
|
50
|
+
"env.telemetry.deleteFailedRetry": "Delete failed (please retry)",
|
|
51
|
+
"env.telemetry.deleteFailed": "Delete failed",
|
|
52
|
+
"env.telemetry.privacyLink": "Privacy policy",
|
|
53
|
+
"env.section.language": "Language",
|
|
54
|
+
"env.language.row": "Language",
|
|
55
|
+
"env.language.ko": "한국어",
|
|
56
|
+
"env.language.en": "English",
|
|
57
|
+
"permissions.section.device": "Device Permissions",
|
|
58
|
+
"location.section.current": "Current Location",
|
|
59
|
+
"location.row.latitude": "Latitude",
|
|
60
|
+
"location.row.longitude": "Longitude",
|
|
61
|
+
"location.row.accuracy": "Accuracy",
|
|
62
|
+
"device.section.modes": "Device API Modes",
|
|
63
|
+
"device.row.camera": "Camera",
|
|
64
|
+
"device.row.photos": "Photos",
|
|
65
|
+
"device.row.location": "Location",
|
|
66
|
+
"device.row.network": "Network",
|
|
67
|
+
"device.row.clipboard": "Clipboard",
|
|
68
|
+
"device.section.mockImages": "Mock Images ({count})",
|
|
69
|
+
"device.btn.add": "+ Add",
|
|
70
|
+
"device.btn.useDefaults": "Use defaults",
|
|
71
|
+
"device.btn.clear": "Clear",
|
|
72
|
+
"device.prompt.camera.title": "Camera Prompt — Select an image",
|
|
73
|
+
"device.prompt.photos.title": "Photos Prompt — Select images",
|
|
74
|
+
"device.prompt.location.title": "Location Prompt — Enter coordinates",
|
|
75
|
+
"device.prompt.locationUpdate.title": "Location Update — Send coordinates",
|
|
76
|
+
"device.prompt.fallbackTitle": "Prompt: {type}",
|
|
77
|
+
"device.prompt.label.lat": "Lat",
|
|
78
|
+
"device.prompt.label.lng": "Lng",
|
|
79
|
+
"device.prompt.send": "Send",
|
|
80
|
+
"device.prompt.cancel": "Cancel",
|
|
81
|
+
"viewport.section.device": "Device",
|
|
82
|
+
"viewport.row.preset": "Preset",
|
|
83
|
+
"viewport.row.orientation": "Orientation",
|
|
84
|
+
"viewport.row.notchSide": "Notch side",
|
|
85
|
+
"viewport.section.custom": "Custom size",
|
|
86
|
+
"viewport.row.width": "Width (px)",
|
|
87
|
+
"viewport.row.height": "Height (px)",
|
|
88
|
+
"viewport.section.appearance": "Appearance",
|
|
89
|
+
"viewport.row.showFrame": "Show frame",
|
|
90
|
+
"viewport.row.showAitNavBar": "Show Apps in Toss nav bar",
|
|
91
|
+
"viewport.row.navBarType": "Nav bar type",
|
|
92
|
+
"viewport.status.noConstraint": "No viewport constraint — body fills the window.",
|
|
93
|
+
"viewport.status.cssPhysical": "CSS / physical",
|
|
94
|
+
"viewport.status.safeArea": "Safe area",
|
|
95
|
+
"viewport.status.aitNavBar": "AIT nav bar",
|
|
96
|
+
"viewport.status.aitNavBarValue": "{height}px (excl. SafeArea) · {type}",
|
|
97
|
+
"viewport.orientation.autoSuffix": "{orient} (auto)",
|
|
98
|
+
"iap.section.simulator": "IAP Simulator",
|
|
99
|
+
"iap.row.nextResult": "Next Purchase Result",
|
|
100
|
+
"iap.section.tossPay": "TossPay",
|
|
101
|
+
"iap.row.tossPayResult": "Next Payment Result",
|
|
102
|
+
"iap.section.pending": "Pending Orders ({count})",
|
|
103
|
+
"iap.empty.pending": "(no pending orders)",
|
|
104
|
+
"iap.section.completed": "Completed Orders ({count})",
|
|
105
|
+
"iap.empty.completed": "(no completed orders)",
|
|
106
|
+
"iap.btn.complete": "Complete",
|
|
107
|
+
"iap.label.pending": "PENDING",
|
|
108
|
+
"events.section.navigation": "Navigation Events",
|
|
109
|
+
"events.btn.triggerBack": "Trigger Back Event",
|
|
110
|
+
"events.btn.triggerHome": "Trigger Home Event",
|
|
111
|
+
"events.section.login": "Login",
|
|
112
|
+
"events.row.loggedIn": "Logged In",
|
|
113
|
+
"events.row.tossLoginIntegrated": "Toss Login Integrated",
|
|
114
|
+
"analytics.section.log": "Analytics Log ({count})",
|
|
115
|
+
"analytics.btn.clear": "Clear",
|
|
116
|
+
"storage.section.title": "Storage ({count} items)",
|
|
117
|
+
"storage.btn.clearAll": "Clear All",
|
|
118
|
+
"storage.empty": "No items in storage",
|
|
119
|
+
"presets.section.builtIn": "Built-in scenarios",
|
|
120
|
+
"presets.section.saved": "Saved presets ({count})",
|
|
121
|
+
"presets.section.save": "Save",
|
|
122
|
+
"presets.save.description": "Capture network / permissions / auth / IAP / ads / payment slices.",
|
|
123
|
+
"presets.btn.saveCurrent": "Save current as preset",
|
|
124
|
+
"presets.btn.apply": "Apply",
|
|
125
|
+
"presets.btn.reApply": "Re-apply",
|
|
126
|
+
"presets.btn.delete": "Delete",
|
|
127
|
+
"presets.empty.saved": "No saved presets yet.",
|
|
128
|
+
"presets.empty.builtIn": "No built-in presets.",
|
|
129
|
+
"presets.prompt.label": "Preset label?",
|
|
130
|
+
"presets.confirm.delete": "Delete preset \"{label}\"?",
|
|
131
|
+
"ads.section.state": "Ads State",
|
|
132
|
+
"ads.row.isLoaded": "isLoaded",
|
|
133
|
+
"ads.row.forceNoFill": "Force \"no fill\"",
|
|
134
|
+
"ads.empty.events": "No events yet",
|
|
135
|
+
"ads.section.googleAdMob": "GoogleAdMob",
|
|
136
|
+
"ads.section.tossAds": "TossAds",
|
|
137
|
+
"ads.section.fullScreenAd": "FullScreenAd",
|
|
138
|
+
"ads.btn.load": "Load",
|
|
139
|
+
"ads.btn.show": "Show",
|
|
140
|
+
"notifications.section.title": "requestNotificationAgreement",
|
|
141
|
+
"notifications.option.newAgreement": "newAgreement (first-time agree)",
|
|
142
|
+
"notifications.option.alreadyAgreed": "alreadyAgreed (already opted-in)",
|
|
143
|
+
"notifications.option.agreementRejected": "agreementRejected (user declined)"
|
|
144
|
+
};
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/i18n/ko.ts
|
|
147
|
+
const ko = {
|
|
148
|
+
"panel.title": "AIT DevTools",
|
|
149
|
+
"panel.toggle.title": "AIT DevTools",
|
|
150
|
+
"panel.close": "Close",
|
|
151
|
+
"panel.editMode.on": "EDIT",
|
|
152
|
+
"panel.editMode.off": "READ-ONLY",
|
|
153
|
+
"panel.editMode.toggleTitle": "패널 편집 모드 전환",
|
|
154
|
+
"panel.tabError": "\"{tab}\" 탭 렌더링 중 오류가 발생했습니다.",
|
|
155
|
+
"panel.tab.env": "Environment",
|
|
156
|
+
"panel.tab.presets": "Presets",
|
|
157
|
+
"panel.tab.viewport": "Viewport",
|
|
158
|
+
"panel.tab.permissions": "Permissions",
|
|
159
|
+
"panel.tab.notifications": "Notifications",
|
|
160
|
+
"panel.tab.location": "Location",
|
|
161
|
+
"panel.tab.device": "Device",
|
|
162
|
+
"panel.tab.iap": "IAP",
|
|
163
|
+
"panel.tab.ads": "Ads",
|
|
164
|
+
"panel.tab.events": "Events",
|
|
165
|
+
"panel.tab.analytics": "Analytics",
|
|
166
|
+
"panel.tab.storage": "Storage",
|
|
167
|
+
"common.readOnly": "읽기 전용 — mock 응답은 빌드 타임에 고정됩니다.",
|
|
168
|
+
"toast.consent.title": "익명 사용 통계를 보낼까요?",
|
|
169
|
+
"toast.consent.body": "도구 개선을 위해 익명 이벤트만 수집해요. 언제든 환경 탭에서 끌 수 있어요.",
|
|
170
|
+
"toast.consent.learnMore": "더 알아보기",
|
|
171
|
+
"toast.consent.accept": "네, 보낼게요",
|
|
172
|
+
"toast.consent.deny": "아니요",
|
|
173
|
+
"env.section.platform": "Platform",
|
|
174
|
+
"env.row.os": "OS",
|
|
175
|
+
"env.row.appVersion": "App Version",
|
|
176
|
+
"env.row.environment": "Environment",
|
|
177
|
+
"env.row.locale": "Locale",
|
|
178
|
+
"env.section.network": "Network",
|
|
179
|
+
"env.row.networkStatus": "Status",
|
|
180
|
+
"env.section.safeArea": "Safe Area Insets",
|
|
181
|
+
"env.row.safeArea.top": "Top",
|
|
182
|
+
"env.row.safeArea.bottom": "Bottom",
|
|
183
|
+
"env.telemetry.section": "Telemetry",
|
|
184
|
+
"env.telemetry.row": "Telemetry",
|
|
185
|
+
"env.telemetry.on": "On",
|
|
186
|
+
"env.telemetry.off": "Off",
|
|
187
|
+
"env.telemetry.turnOn": "Turn on",
|
|
188
|
+
"env.telemetry.turnOff": "Turn off",
|
|
189
|
+
"env.telemetry.anonIdLabel": "anon_id: {value}",
|
|
190
|
+
"env.telemetry.anonIdNotSet": "(not yet set)",
|
|
191
|
+
"env.telemetry.anonIdCopyTitle": "전체 anon_id 복사",
|
|
192
|
+
"env.telemetry.deleteBtn": "내 데이터 삭제",
|
|
193
|
+
"env.telemetry.deleting": "삭제 중…",
|
|
194
|
+
"env.telemetry.deleted": "삭제 완료",
|
|
195
|
+
"env.telemetry.deleteFailedRetry": "삭제 실패 (다시 시도해주세요)",
|
|
196
|
+
"env.telemetry.deleteFailed": "삭제 실패",
|
|
197
|
+
"env.telemetry.privacyLink": "개인정보 처리방침",
|
|
198
|
+
"env.section.language": "Language",
|
|
199
|
+
"env.language.row": "Language",
|
|
200
|
+
"env.language.ko": "한국어",
|
|
201
|
+
"env.language.en": "English",
|
|
202
|
+
"permissions.section.device": "Device Permissions",
|
|
203
|
+
"location.section.current": "Current Location",
|
|
204
|
+
"location.row.latitude": "Latitude",
|
|
205
|
+
"location.row.longitude": "Longitude",
|
|
206
|
+
"location.row.accuracy": "Accuracy",
|
|
207
|
+
"device.section.modes": "Device API Modes",
|
|
208
|
+
"device.row.camera": "Camera",
|
|
209
|
+
"device.row.photos": "Photos",
|
|
210
|
+
"device.row.location": "Location",
|
|
211
|
+
"device.row.network": "Network",
|
|
212
|
+
"device.row.clipboard": "Clipboard",
|
|
213
|
+
"device.section.mockImages": "Mock Images ({count})",
|
|
214
|
+
"device.btn.add": "+ Add",
|
|
215
|
+
"device.btn.useDefaults": "Use defaults",
|
|
216
|
+
"device.btn.clear": "Clear",
|
|
217
|
+
"device.prompt.camera.title": "Camera Prompt — 이미지를 선택하세요",
|
|
218
|
+
"device.prompt.photos.title": "Photos Prompt — 이미지를 선택하세요",
|
|
219
|
+
"device.prompt.location.title": "Location Prompt — 좌표 입력",
|
|
220
|
+
"device.prompt.locationUpdate.title": "Location Update — 좌표 전송",
|
|
221
|
+
"device.prompt.fallbackTitle": "Prompt: {type}",
|
|
222
|
+
"device.prompt.label.lat": "Lat",
|
|
223
|
+
"device.prompt.label.lng": "Lng",
|
|
224
|
+
"device.prompt.send": "Send",
|
|
225
|
+
"device.prompt.cancel": "Cancel",
|
|
226
|
+
"viewport.section.device": "Device",
|
|
227
|
+
"viewport.row.preset": "Preset",
|
|
228
|
+
"viewport.row.orientation": "Orientation",
|
|
229
|
+
"viewport.row.notchSide": "Notch side",
|
|
230
|
+
"viewport.section.custom": "Custom size",
|
|
231
|
+
"viewport.row.width": "Width (px)",
|
|
232
|
+
"viewport.row.height": "Height (px)",
|
|
233
|
+
"viewport.section.appearance": "Appearance",
|
|
234
|
+
"viewport.row.showFrame": "Show frame",
|
|
235
|
+
"viewport.row.showAitNavBar": "Apps in Toss 내비게이션 바 표시",
|
|
236
|
+
"viewport.row.navBarType": "Nav bar type",
|
|
237
|
+
"viewport.status.noConstraint": "뷰포트 제약 없음 — body가 창을 가득 채웁니다.",
|
|
238
|
+
"viewport.status.cssPhysical": "CSS / physical",
|
|
239
|
+
"viewport.status.safeArea": "Safe area",
|
|
240
|
+
"viewport.status.aitNavBar": "AIT nav bar",
|
|
241
|
+
"viewport.status.aitNavBarValue": "{height}px (excl. SafeArea) · {type}",
|
|
242
|
+
"viewport.orientation.autoSuffix": "{orient} (auto)",
|
|
243
|
+
"iap.section.simulator": "IAP Simulator",
|
|
244
|
+
"iap.row.nextResult": "Next Purchase Result",
|
|
245
|
+
"iap.section.tossPay": "TossPay",
|
|
246
|
+
"iap.row.tossPayResult": "Next Payment Result",
|
|
247
|
+
"iap.section.pending": "Pending Orders ({count})",
|
|
248
|
+
"iap.empty.pending": "(대기 중인 주문 없음)",
|
|
249
|
+
"iap.section.completed": "Completed Orders ({count})",
|
|
250
|
+
"iap.empty.completed": "(완료된 주문 없음)",
|
|
251
|
+
"iap.btn.complete": "Complete",
|
|
252
|
+
"iap.label.pending": "PENDING",
|
|
253
|
+
"events.section.navigation": "Navigation Events",
|
|
254
|
+
"events.btn.triggerBack": "Back 이벤트 발생",
|
|
255
|
+
"events.btn.triggerHome": "Home 이벤트 발생",
|
|
256
|
+
"events.section.login": "Login",
|
|
257
|
+
"events.row.loggedIn": "Logged In",
|
|
258
|
+
"events.row.tossLoginIntegrated": "Toss Login Integrated",
|
|
259
|
+
"analytics.section.log": "Analytics Log ({count})",
|
|
260
|
+
"analytics.btn.clear": "Clear",
|
|
261
|
+
"storage.section.title": "Storage ({count} items)",
|
|
262
|
+
"storage.btn.clearAll": "Clear All",
|
|
263
|
+
"storage.empty": "저장된 항목이 없습니다",
|
|
264
|
+
"presets.section.builtIn": "Built-in scenarios",
|
|
265
|
+
"presets.section.saved": "Saved presets ({count})",
|
|
266
|
+
"presets.section.save": "Save",
|
|
267
|
+
"presets.save.description": "network / permissions / auth / IAP / ads / payment 슬라이스를 캡처합니다.",
|
|
268
|
+
"presets.btn.saveCurrent": "현재 상태를 프리셋으로 저장",
|
|
269
|
+
"presets.btn.apply": "Apply",
|
|
270
|
+
"presets.btn.reApply": "Re-apply",
|
|
271
|
+
"presets.btn.delete": "Delete",
|
|
272
|
+
"presets.empty.saved": "저장된 프리셋이 아직 없습니다.",
|
|
273
|
+
"presets.empty.builtIn": "내장 프리셋이 없습니다.",
|
|
274
|
+
"presets.prompt.label": "프리셋 라벨을 입력하세요",
|
|
275
|
+
"presets.confirm.delete": "\"{label}\" 프리셋을 삭제할까요?",
|
|
276
|
+
"ads.section.state": "Ads State",
|
|
277
|
+
"ads.row.isLoaded": "isLoaded",
|
|
278
|
+
"ads.row.forceNoFill": "강제 \"no fill\"",
|
|
279
|
+
"ads.empty.events": "아직 이벤트가 없습니다",
|
|
280
|
+
"ads.section.googleAdMob": "GoogleAdMob",
|
|
281
|
+
"ads.section.tossAds": "TossAds",
|
|
282
|
+
"ads.section.fullScreenAd": "FullScreenAd",
|
|
283
|
+
"ads.btn.load": "Load",
|
|
284
|
+
"ads.btn.show": "Show",
|
|
285
|
+
"notifications.section.title": "requestNotificationAgreement",
|
|
286
|
+
"notifications.option.newAgreement": "newAgreement (최초 동의)",
|
|
287
|
+
"notifications.option.alreadyAgreed": "alreadyAgreed (이미 동의됨)",
|
|
288
|
+
"notifications.option.agreementRejected": "agreementRejected (사용자 거절)"
|
|
289
|
+
};
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/i18n/index.ts
|
|
292
|
+
/**
|
|
293
|
+
* Vanilla TS i18n for the floating DevTools panel.
|
|
294
|
+
*
|
|
295
|
+
* Public surface:
|
|
296
|
+
* - `t(key, vars?)` — look up a UI string, with `{name}` placeholder
|
|
297
|
+
* interpolation. Falls back to the key itself if a translation is missing.
|
|
298
|
+
* - `getLocale()` / `setLocale(locale)` — read/persist the active locale.
|
|
299
|
+
* `setLocale` dispatches `__ait:localechange` so the panel can remount.
|
|
300
|
+
* - `detectLocale()` — first-run heuristic from `navigator.language`.
|
|
301
|
+
*
|
|
302
|
+
* `ko` is the source of truth (keys are typed from it). `en` is also a full
|
|
303
|
+
* `Record<StringKey, string>` (devtools is developer-facing, en is a real
|
|
304
|
+
* audience). The `Partial` lookup table preserves the runtime `?? key` safety
|
|
305
|
+
* net even though we ship complete catalogs today.
|
|
306
|
+
*/
|
|
307
|
+
const LOCALE_STORAGE_KEY = "__ait_locale";
|
|
308
|
+
const LOCALE_CHANGE_EVENT = "__ait:localechange";
|
|
309
|
+
const tables = {
|
|
310
|
+
ko,
|
|
311
|
+
en
|
|
312
|
+
};
|
|
313
|
+
let currentLocale = null;
|
|
314
|
+
function safeReadStorage() {
|
|
315
|
+
if (typeof localStorage === "undefined") return null;
|
|
316
|
+
try {
|
|
317
|
+
const raw = localStorage.getItem(LOCALE_STORAGE_KEY);
|
|
318
|
+
if (raw === "ko" || raw === "en") return raw;
|
|
319
|
+
} catch {}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
function safeWriteStorage(locale) {
|
|
323
|
+
if (typeof localStorage === "undefined") return;
|
|
324
|
+
try {
|
|
325
|
+
localStorage.setItem(LOCALE_STORAGE_KEY, locale);
|
|
326
|
+
} catch {}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Read `navigator.language` and decide a locale. `ko` (and `ko-*`) → `'ko'`,
|
|
330
|
+
* everything else → `'en'`. Pure function; does not touch storage.
|
|
331
|
+
*/
|
|
332
|
+
function detectLocale() {
|
|
333
|
+
if (typeof navigator === "undefined") return "en";
|
|
334
|
+
const lang = navigator.language ?? "";
|
|
335
|
+
return /^ko\b/i.test(lang) ? "ko" : "en";
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Resolve the active locale, in order:
|
|
339
|
+
* 1. previously set in-memory value (set by `setLocale`)
|
|
340
|
+
* 2. localStorage `__ait_locale`
|
|
341
|
+
* 3. `detectLocale()` from navigator
|
|
342
|
+
*/
|
|
343
|
+
function getLocale() {
|
|
344
|
+
if (currentLocale) return currentLocale;
|
|
345
|
+
currentLocale = safeReadStorage() ?? detectLocale();
|
|
346
|
+
return currentLocale;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Persist a locale choice and notify listeners. The panel listens for
|
|
350
|
+
* `__ait:localechange` and re-mounts so every string re-evaluates.
|
|
351
|
+
*/
|
|
352
|
+
function setLocale(locale) {
|
|
353
|
+
currentLocale = locale;
|
|
354
|
+
safeWriteStorage(locale);
|
|
355
|
+
if (typeof window !== "undefined") window.dispatchEvent(new CustomEvent(LOCALE_CHANGE_EVENT));
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Look up a UI string for the current locale. Falls back to the key if missing,
|
|
359
|
+
* so a forgotten key surfaces visibly rather than rendering empty.
|
|
360
|
+
*/
|
|
361
|
+
function t(key, vars) {
|
|
362
|
+
const raw = tables[getLocale()][key] ?? key;
|
|
363
|
+
if (!vars) return raw;
|
|
364
|
+
return raw.replace(/\{(\w+)\}/g, (match, name) => {
|
|
365
|
+
const value = vars[name];
|
|
366
|
+
return value === void 0 ? match : String(value);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
//#endregion
|
|
1
370
|
//#region src/mock/state.ts
|
|
2
371
|
const DEFAULT_STATE = {
|
|
3
372
|
platform: "ios",
|
|
@@ -224,9 +593,6 @@ if (typeof window !== "undefined") window.__ait = aitState;
|
|
|
224
593
|
/**
|
|
225
594
|
* Consent toast UI — vanilla DOM, fixed bottom-right.
|
|
226
595
|
*
|
|
227
|
-
* Ko-only for now — devtools has no i18n layer.
|
|
228
|
-
* TODO: revisit when/if an i18n layer is added to the panel.
|
|
229
|
-
*
|
|
230
596
|
* Shows once per "undecided + reprompt window cleared" session.
|
|
231
597
|
* Calls onAccept / onDeny callbacks; caller is responsible for persisting state.
|
|
232
598
|
*/
|
|
@@ -318,26 +684,26 @@ function showConsentToast({ onAccept, onDeny }) {
|
|
|
318
684
|
toast.id = TOAST_ID;
|
|
319
685
|
const header = document.createElement("div");
|
|
320
686
|
header.className = "ait-toast-header";
|
|
321
|
-
header.textContent = "
|
|
687
|
+
header.textContent = t("toast.consent.title");
|
|
322
688
|
const body = document.createElement("div");
|
|
323
689
|
body.className = "ait-toast-body";
|
|
324
|
-
body.textContent = "
|
|
690
|
+
body.textContent = t("toast.consent.body");
|
|
325
691
|
const learnMore = document.createElement("a");
|
|
326
692
|
learnMore.className = "ait-toast-link";
|
|
327
693
|
learnMore.href = "https://docs.aitc.dev/privacy";
|
|
328
694
|
learnMore.target = "_blank";
|
|
329
695
|
learnMore.rel = "noopener noreferrer";
|
|
330
|
-
learnMore.textContent = "
|
|
696
|
+
learnMore.textContent = t("toast.consent.learnMore");
|
|
331
697
|
const yesBtn = document.createElement("button");
|
|
332
698
|
yesBtn.className = "ait-toast-btn-primary";
|
|
333
|
-
yesBtn.textContent = "
|
|
699
|
+
yesBtn.textContent = t("toast.consent.accept");
|
|
334
700
|
yesBtn.addEventListener("click", () => {
|
|
335
701
|
removeToast();
|
|
336
702
|
onAccept();
|
|
337
703
|
});
|
|
338
704
|
const noBtn = document.createElement("button");
|
|
339
705
|
noBtn.className = "ait-toast-btn-secondary";
|
|
340
|
-
noBtn.textContent = "
|
|
706
|
+
noBtn.textContent = t("toast.consent.deny");
|
|
341
707
|
noBtn.addEventListener("click", () => {
|
|
342
708
|
removeToast();
|
|
343
709
|
onDeny();
|
|
@@ -582,7 +948,7 @@ function readGlobalString(key) {
|
|
|
582
948
|
}
|
|
583
949
|
const TELEMETRY_ENDPOINT = readGlobalString("__TELEMETRY_ENDPOINT__") ?? "https://t.aitc.dev";
|
|
584
950
|
function getVersion() {
|
|
585
|
-
return "0.1.
|
|
951
|
+
return "0.1.19";
|
|
586
952
|
}
|
|
587
953
|
let panelVisibleSince = null;
|
|
588
954
|
let accumulatedMs = 0;
|
|
@@ -691,7 +1057,7 @@ function inputRow(label, value, onChange, disabled = false) {
|
|
|
691
1057
|
return h("div", { className: "ait-row" }, h("label", {}, label), input);
|
|
692
1058
|
}
|
|
693
1059
|
function monitoringNotice() {
|
|
694
|
-
return h("div", { className: "ait-monitoring-notice" }, "
|
|
1060
|
+
return h("div", { className: "ait-monitoring-notice" }, t("common.readOnly"));
|
|
695
1061
|
}
|
|
696
1062
|
const PANEL_STYLES = `
|
|
697
1063
|
.ait-panel-toggle {
|
|
@@ -1671,7 +2037,7 @@ function renderPromptBanner() {
|
|
|
1671
2037
|
if (!pendingPrompt) return null;
|
|
1672
2038
|
const banner = h("div", { className: "ait-prompt-banner" });
|
|
1673
2039
|
if (pendingPrompt.type === "camera") {
|
|
1674
|
-
banner.append(h("div", { className: "ait-prompt-title" }, "
|
|
2040
|
+
banner.append(h("div", { className: "ait-prompt-title" }, t("device.prompt.camera.title")));
|
|
1675
2041
|
const input = h("input", {
|
|
1676
2042
|
type: "file",
|
|
1677
2043
|
accept: "image/*",
|
|
@@ -1686,7 +2052,7 @@ function renderPromptBanner() {
|
|
|
1686
2052
|
});
|
|
1687
2053
|
banner.appendChild(input);
|
|
1688
2054
|
} else if (pendingPrompt.type === "photos") {
|
|
1689
|
-
banner.append(h("div", { className: "ait-prompt-title" }, "
|
|
2055
|
+
banner.append(h("div", { className: "ait-prompt-title" }, t("device.prompt.photos.title")));
|
|
1690
2056
|
const input = h("input", {
|
|
1691
2057
|
type: "file",
|
|
1692
2058
|
accept: "image/*",
|
|
@@ -1704,7 +2070,7 @@ function renderPromptBanner() {
|
|
|
1704
2070
|
});
|
|
1705
2071
|
banner.appendChild(input);
|
|
1706
2072
|
} else if (pendingPrompt.type === "location" || pendingPrompt.type === "location-update") {
|
|
1707
|
-
banner.append(h("div", { className: "ait-prompt-title" }, pendingPrompt.type === "location" ? "
|
|
2073
|
+
banner.append(h("div", { className: "ait-prompt-title" }, pendingPrompt.type === "location" ? t("device.prompt.location.title") : t("device.prompt.locationUpdate.title")));
|
|
1708
2074
|
const latInput = h("input", {
|
|
1709
2075
|
className: "ait-input",
|
|
1710
2076
|
value: String(aitState.state.location.coords.latitude),
|
|
@@ -1715,7 +2081,7 @@ function renderPromptBanner() {
|
|
|
1715
2081
|
value: String(aitState.state.location.coords.longitude),
|
|
1716
2082
|
style: "width:80px"
|
|
1717
2083
|
});
|
|
1718
|
-
const sendBtn = h("button", { className: "ait-btn ait-btn-sm" }, "
|
|
2084
|
+
const sendBtn = h("button", { className: "ait-btn ait-btn-sm" }, t("device.prompt.send"));
|
|
1719
2085
|
sendBtn.addEventListener("click", () => {
|
|
1720
2086
|
const loc = {
|
|
1721
2087
|
coords: {
|
|
@@ -1731,12 +2097,12 @@ function renderPromptBanner() {
|
|
|
1731
2097
|
};
|
|
1732
2098
|
resolvePrompt(pendingPrompt.type, loc);
|
|
1733
2099
|
});
|
|
1734
|
-
banner.append(h("div", { className: "ait-prompt-input-row" }, h("label", {}, "
|
|
1735
|
-
} else banner.append(h("div", { className: "ait-prompt-title" },
|
|
2100
|
+
banner.append(h("div", { className: "ait-prompt-input-row" }, h("label", {}, t("device.prompt.label.lat")), latInput, h("label", {}, t("device.prompt.label.lng")), lngInput, sendBtn));
|
|
2101
|
+
} else banner.append(h("div", { className: "ait-prompt-title" }, t("device.prompt.fallbackTitle", { type: pendingPrompt.type })));
|
|
1736
2102
|
const cancelBtn = h("button", {
|
|
1737
2103
|
className: "ait-btn ait-btn-sm ait-btn-danger",
|
|
1738
2104
|
style: "margin-top:8px"
|
|
1739
|
-
}, "
|
|
2105
|
+
}, t("device.prompt.cancel"));
|
|
1740
2106
|
cancelBtn.addEventListener("click", () => {
|
|
1741
2107
|
pendingPrompt = null;
|
|
1742
2108
|
window.dispatchEvent(new CustomEvent("__ait:prompt-cancel"));
|
|
@@ -1754,9 +2120,9 @@ function renderDeviceTab() {
|
|
|
1754
2120
|
const promptBanner = renderPromptBanner();
|
|
1755
2121
|
if (promptBanner) container.appendChild(promptBanner);
|
|
1756
2122
|
}
|
|
1757
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2123
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("device.section.modes")), ...[
|
|
1758
2124
|
{
|
|
1759
|
-
|
|
2125
|
+
labelKey: "device.row.camera",
|
|
1760
2126
|
key: "camera",
|
|
1761
2127
|
options: [
|
|
1762
2128
|
"mock",
|
|
@@ -1765,7 +2131,7 @@ function renderDeviceTab() {
|
|
|
1765
2131
|
]
|
|
1766
2132
|
},
|
|
1767
2133
|
{
|
|
1768
|
-
|
|
2134
|
+
labelKey: "device.row.photos",
|
|
1769
2135
|
key: "photos",
|
|
1770
2136
|
options: [
|
|
1771
2137
|
"mock",
|
|
@@ -1774,7 +2140,7 @@ function renderDeviceTab() {
|
|
|
1774
2140
|
]
|
|
1775
2141
|
},
|
|
1776
2142
|
{
|
|
1777
|
-
|
|
2143
|
+
labelKey: "device.row.location",
|
|
1778
2144
|
key: "location",
|
|
1779
2145
|
options: [
|
|
1780
2146
|
"mock",
|
|
@@ -1783,16 +2149,16 @@ function renderDeviceTab() {
|
|
|
1783
2149
|
]
|
|
1784
2150
|
},
|
|
1785
2151
|
{
|
|
1786
|
-
|
|
2152
|
+
labelKey: "device.row.network",
|
|
1787
2153
|
key: "network",
|
|
1788
2154
|
options: ["mock", "web"]
|
|
1789
2155
|
},
|
|
1790
2156
|
{
|
|
1791
|
-
|
|
2157
|
+
labelKey: "device.row.clipboard",
|
|
1792
2158
|
key: "clipboard",
|
|
1793
2159
|
options: ["mock", "web"]
|
|
1794
2160
|
}
|
|
1795
|
-
].map((entry) => selectRow(entry.
|
|
2161
|
+
].map((entry) => selectRow(t(entry.labelKey), entry.options, s.deviceModes[entry.key], (v) => {
|
|
1796
2162
|
aitState.patch("deviceModes", { [entry.key]: v });
|
|
1797
2163
|
}, disabled))));
|
|
1798
2164
|
const images = s.mockData.images;
|
|
@@ -1810,7 +2176,7 @@ function renderDeviceTab() {
|
|
|
1810
2176
|
thumb.append(img, removeBtn);
|
|
1811
2177
|
imageGrid.appendChild(thumb);
|
|
1812
2178
|
});
|
|
1813
|
-
const addBtn = h("button", { className: "ait-btn-secondary" }, "
|
|
2179
|
+
const addBtn = h("button", { className: "ait-btn-secondary" }, t("device.btn.add"));
|
|
1814
2180
|
addBtn.addEventListener("click", () => {
|
|
1815
2181
|
const input = document.createElement("input");
|
|
1816
2182
|
input.type = "file";
|
|
@@ -1829,17 +2195,17 @@ function renderDeviceTab() {
|
|
|
1829
2195
|
input.click();
|
|
1830
2196
|
});
|
|
1831
2197
|
if (disabled) addBtn.disabled = true;
|
|
1832
|
-
const defaultsBtn = h("button", { className: "ait-btn-secondary" }, "
|
|
2198
|
+
const defaultsBtn = h("button", { className: "ait-btn-secondary" }, t("device.btn.useDefaults"));
|
|
1833
2199
|
defaultsBtn.addEventListener("click", () => {
|
|
1834
2200
|
aitState.patch("mockData", { images: [...getDefaultPlaceholderImages()] });
|
|
1835
2201
|
});
|
|
1836
2202
|
if (disabled) defaultsBtn.disabled = true;
|
|
1837
|
-
const clearImagesBtn = h("button", { className: "ait-btn-secondary" }, "
|
|
2203
|
+
const clearImagesBtn = h("button", { className: "ait-btn-secondary" }, t("device.btn.clear"));
|
|
1838
2204
|
clearImagesBtn.addEventListener("click", () => {
|
|
1839
2205
|
aitState.patch("mockData", { images: [] });
|
|
1840
2206
|
});
|
|
1841
2207
|
if (disabled) clearImagesBtn.disabled = true;
|
|
1842
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" },
|
|
2208
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("device.section.mockImages", { count: images.length })), imageGrid, h("div", { className: "ait-btn-row" }, addBtn, defaultsBtn, clearImagesBtn)));
|
|
1843
2209
|
return container;
|
|
1844
2210
|
}
|
|
1845
2211
|
//#endregion
|
|
@@ -1956,7 +2322,7 @@ function statusRow(label, value) {
|
|
|
1956
2322
|
}
|
|
1957
2323
|
function lastEventLine() {
|
|
1958
2324
|
const last = aitState.state.ads.lastEvent;
|
|
1959
|
-
if (!last) return h("div", { className: "ait-log-entry" }, h("span", { style: "color:#555" }, "
|
|
2325
|
+
if (!last) return h("div", { className: "ait-log-entry" }, h("span", { style: "color:#555" }, t("ads.empty.events")));
|
|
1960
2326
|
const time = new Date(last.timestamp).toLocaleTimeString();
|
|
1961
2327
|
return h("div", { className: "ait-log-entry" }, h("span", {
|
|
1962
2328
|
className: "ait-log-type",
|
|
@@ -1964,8 +2330,8 @@ function lastEventLine() {
|
|
|
1964
2330
|
}, last.type), h("span", { className: "ait-log-time" }, time));
|
|
1965
2331
|
}
|
|
1966
2332
|
function adSection(title, onLoad, onShow, disabled) {
|
|
1967
|
-
const loadBtn = h("button", { className: "ait-btn ait-btn-sm" }, "
|
|
1968
|
-
const showBtn = h("button", { className: "ait-btn ait-btn-sm" }, "
|
|
2333
|
+
const loadBtn = h("button", { className: "ait-btn ait-btn-sm" }, t("ads.btn.load"));
|
|
2334
|
+
const showBtn = h("button", { className: "ait-btn ait-btn-sm" }, t("ads.btn.show"));
|
|
1969
2335
|
if (disabled) {
|
|
1970
2336
|
loadBtn.disabled = true;
|
|
1971
2337
|
showBtn.disabled = true;
|
|
@@ -1988,7 +2354,7 @@ function renderAdsTab() {
|
|
|
1988
2354
|
forceNoFillCb.addEventListener("change", () => {
|
|
1989
2355
|
aitState.patch("ads", { forceNoFill: forceNoFillCb.checked });
|
|
1990
2356
|
});
|
|
1991
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2357
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("ads.section.state")), statusRow(t("ads.row.isLoaded"), String(s.ads.isLoaded)), h("div", { className: "ait-row" }, h("label", {}, t("ads.row.forceNoFill")), forceNoFillCb), lastEventLine()), adSection(t("ads.section.googleAdMob"), () => {
|
|
1992
2358
|
GoogleAdMob.loadAppsInTossAdMob({
|
|
1993
2359
|
onEvent: (e) => recordEvent(e.type),
|
|
1994
2360
|
onError: (err) => recordError(err.message)
|
|
@@ -1998,7 +2364,7 @@ function renderAdsTab() {
|
|
|
1998
2364
|
onEvent: (e) => recordEvent(e.type),
|
|
1999
2365
|
onError: (err) => recordError(err.message)
|
|
2000
2366
|
});
|
|
2001
|
-
}, disabled), adSection("
|
|
2367
|
+
}, disabled), adSection(t("ads.section.tossAds"), () => {
|
|
2002
2368
|
if (aitState.state.ads.forceNoFill) {
|
|
2003
2369
|
recordError("No fill");
|
|
2004
2370
|
return;
|
|
@@ -2015,7 +2381,7 @@ function renderAdsTab() {
|
|
|
2015
2381
|
recordEvent("dismissed");
|
|
2016
2382
|
aitState.patch("ads", { isLoaded: false });
|
|
2017
2383
|
}, 1500);
|
|
2018
|
-
}, disabled), adSection("
|
|
2384
|
+
}, disabled), adSection(t("ads.section.fullScreenAd"), () => {
|
|
2019
2385
|
loadFullScreenAd({
|
|
2020
2386
|
onEvent: (e) => recordEvent(e.type),
|
|
2021
2387
|
onError: (err) => recordError(err.message)
|
|
@@ -2035,13 +2401,13 @@ function renderAnalyticsTab() {
|
|
|
2035
2401
|
const container = h("div");
|
|
2036
2402
|
if (disabled) container.appendChild(monitoringNotice());
|
|
2037
2403
|
const logs = aitState.state.analyticsLog;
|
|
2038
|
-
const clearBtn = h("button", { className: "ait-btn ait-btn-sm ait-btn-danger" }, "
|
|
2404
|
+
const clearBtn = h("button", { className: "ait-btn ait-btn-sm ait-btn-danger" }, t("analytics.btn.clear"));
|
|
2039
2405
|
if (disabled) clearBtn.disabled = true;
|
|
2040
2406
|
clearBtn.addEventListener("click", () => {
|
|
2041
2407
|
aitState.update({ analyticsLog: [] });
|
|
2042
2408
|
});
|
|
2043
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-row" }, h("div", { className: "ait-section-title" },
|
|
2044
|
-
return h("div", { className: "ait-log-entry" }, h("span", { className: "ait-log-time" }, new Date(entry.timestamp).toLocaleTimeString(
|
|
2409
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-row" }, h("div", { className: "ait-section-title" }, t("analytics.section.log", { count: logs.length })), clearBtn), ...logs.slice(-30).reverse().map((entry) => {
|
|
2410
|
+
return h("div", { className: "ait-log-entry" }, h("span", { className: "ait-log-time" }, new Date(entry.timestamp).toLocaleTimeString()), h("span", { className: "ait-log-type" }, entry.type), JSON.stringify(entry.params));
|
|
2045
2411
|
})));
|
|
2046
2412
|
return container;
|
|
2047
2413
|
}
|
|
@@ -2052,7 +2418,7 @@ function renderEnvironmentTab() {
|
|
|
2052
2418
|
const disabled = !s.panelEditable;
|
|
2053
2419
|
const container = h("div");
|
|
2054
2420
|
if (disabled) container.appendChild(monitoringNotice());
|
|
2055
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2421
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("env.section.platform")), selectRow(t("env.row.os"), ["ios", "android"], s.platform, (v) => aitState.update({ platform: v }), disabled), inputRow(t("env.row.appVersion"), s.appVersion, (v) => aitState.update({ appVersion: v }), disabled), selectRow(t("env.row.environment"), ["toss", "sandbox"], s.environment, (v) => aitState.update({ environment: v }), disabled), inputRow(t("env.row.locale"), s.locale, (v) => aitState.update({ locale: v }), disabled)), h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("env.section.network")), selectRow(t("env.row.networkStatus"), [
|
|
2056
2422
|
"WIFI",
|
|
2057
2423
|
"4G",
|
|
2058
2424
|
"5G",
|
|
@@ -2061,39 +2427,61 @@ function renderEnvironmentTab() {
|
|
|
2061
2427
|
"OFFLINE",
|
|
2062
2428
|
"WWAN",
|
|
2063
2429
|
"UNKNOWN"
|
|
2064
|
-
], s.networkStatus, (v) => aitState.update({ networkStatus: v }), disabled)), h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2430
|
+
], s.networkStatus, (v) => aitState.update({ networkStatus: v }), disabled)), h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("env.section.safeArea")), inputRow(t("env.row.safeArea.top"), String(s.safeAreaInsets.top), (v) => aitState.patch("safeAreaInsets", { top: Number(v) }), disabled), inputRow(t("env.row.safeArea.bottom"), String(s.safeAreaInsets.bottom), (v) => aitState.patch("safeAreaInsets", { bottom: Number(v) }), disabled)), buildLanguageSection(), buildTelemetrySection());
|
|
2065
2431
|
return container;
|
|
2066
2432
|
}
|
|
2433
|
+
function buildLanguageSection() {
|
|
2434
|
+
const current = getLocale();
|
|
2435
|
+
const select = h("select", { className: "ait-select" });
|
|
2436
|
+
for (const opt of [{
|
|
2437
|
+
value: "ko",
|
|
2438
|
+
labelKey: "env.language.ko"
|
|
2439
|
+
}, {
|
|
2440
|
+
value: "en",
|
|
2441
|
+
labelKey: "env.language.en"
|
|
2442
|
+
}]) {
|
|
2443
|
+
const option = h("option", { value: opt.value }, t(opt.labelKey));
|
|
2444
|
+
if (opt.value === current) option.selected = true;
|
|
2445
|
+
select.appendChild(option);
|
|
2446
|
+
}
|
|
2447
|
+
select.addEventListener("change", () => {
|
|
2448
|
+
setLocale(select.value);
|
|
2449
|
+
});
|
|
2450
|
+
return h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("env.section.language")), h("div", { className: "ait-row" }, h("label", {}, t("env.language.row")), select));
|
|
2451
|
+
}
|
|
2067
2452
|
function buildTelemetrySection() {
|
|
2068
2453
|
const isGranted = readConsentState() === "granted";
|
|
2069
|
-
const statusLabel = h("span", { style: `font-size:12px;font-weight:600;color:${isGranted ? "#4ade80" : "#888"}` }, isGranted ? "
|
|
2454
|
+
const statusLabel = h("span", { style: `font-size:12px;font-weight:600;color:${isGranted ? "#4ade80" : "#888"}` }, isGranted ? t("env.telemetry.on") : t("env.telemetry.off"));
|
|
2070
2455
|
const toggleBtn = h("button", {
|
|
2071
2456
|
className: "ait-btn ait-btn-sm",
|
|
2072
2457
|
style: "font-size:11px"
|
|
2073
|
-
}, isGranted ? "
|
|
2458
|
+
}, isGranted ? t("env.telemetry.turnOff") : t("env.telemetry.turnOn"));
|
|
2074
2459
|
toggleBtn.addEventListener("click", () => {
|
|
2075
2460
|
setConsentViaToggle(!isGranted);
|
|
2076
2461
|
window.dispatchEvent(new CustomEvent("__ait:panel-switch-tab", { detail: { tab: "env" } }));
|
|
2077
2462
|
});
|
|
2078
|
-
const statusRow = h("div", { className: "ait-row" }, h("label", {}, "
|
|
2079
|
-
const
|
|
2463
|
+
const statusRow = h("div", { className: "ait-row" }, h("label", {}, t("env.telemetry.row")), h("span", { style: "display:flex;align-items:center;gap:8px" }, statusLabel, toggleBtn));
|
|
2464
|
+
const rawAnonId = localStorage.getItem("__ait_telemetry:anon_id");
|
|
2465
|
+
const displayAnonId = rawAnonId ?? t("env.telemetry.anonIdNotSet");
|
|
2466
|
+
const truncatedId = displayAnonId.length > 8 ? `${displayAnonId.slice(0, 8)}…` : displayAnonId;
|
|
2080
2467
|
const anonIdEl = h("span", {
|
|
2081
2468
|
style: "font-family:'SF Mono','Menlo',monospace;font-size:11px;color:#95e6cb;cursor:pointer",
|
|
2082
|
-
title: "
|
|
2083
|
-
},
|
|
2469
|
+
title: t("env.telemetry.anonIdCopyTitle")
|
|
2470
|
+
}, t("env.telemetry.anonIdLabel", { value: truncatedId }));
|
|
2084
2471
|
anonIdEl.addEventListener("click", () => {
|
|
2085
|
-
|
|
2472
|
+
if (!rawAnonId) return;
|
|
2473
|
+
navigator.clipboard.writeText(rawAnonId).catch(() => {});
|
|
2086
2474
|
});
|
|
2087
|
-
const deleteBtn = h("button", { className: "ait-btn ait-btn-sm ait-btn-danger" }, "
|
|
2475
|
+
const deleteBtn = h("button", { className: "ait-btn ait-btn-sm ait-btn-danger" }, t("env.telemetry.deleteBtn"));
|
|
2088
2476
|
const deleteStatus = h("span", { style: "font-size:11px;color:#aaa" });
|
|
2089
2477
|
deleteBtn.addEventListener("click", () => {
|
|
2090
2478
|
deleteBtn.disabled = true;
|
|
2091
|
-
deleteStatus.textContent = "
|
|
2479
|
+
deleteStatus.textContent = t("env.telemetry.deleting");
|
|
2092
2480
|
deleteMyData(TELEMETRY_ENDPOINT).then((ok) => {
|
|
2093
|
-
deleteStatus.textContent = ok ? "
|
|
2481
|
+
deleteStatus.textContent = ok ? t("env.telemetry.deleted") : t("env.telemetry.deleteFailedRetry");
|
|
2094
2482
|
deleteBtn.disabled = false;
|
|
2095
2483
|
}).catch(() => {
|
|
2096
|
-
deleteStatus.textContent = "
|
|
2484
|
+
deleteStatus.textContent = t("env.telemetry.deleteFailed");
|
|
2097
2485
|
deleteBtn.disabled = false;
|
|
2098
2486
|
});
|
|
2099
2487
|
});
|
|
@@ -2103,8 +2491,8 @@ function buildTelemetrySection() {
|
|
|
2103
2491
|
rel: "noopener noreferrer",
|
|
2104
2492
|
style: "font-size:11px;color:#666;text-decoration:none"
|
|
2105
2493
|
});
|
|
2106
|
-
privacyLink.textContent = "
|
|
2107
|
-
return h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2494
|
+
privacyLink.textContent = t("env.telemetry.privacyLink");
|
|
2495
|
+
return h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("env.telemetry.section")), statusRow, h("div", { style: "margin-bottom:6px" }, anonIdEl), h("div", {
|
|
2108
2496
|
className: "ait-btn-row",
|
|
2109
2497
|
style: "align-items:center;gap:8px;margin-top:6px"
|
|
2110
2498
|
}, deleteBtn, deleteStatus), h("div", { style: "margin-top:8px" }, privacyLink));
|
|
@@ -2115,15 +2503,15 @@ function renderEventsTab() {
|
|
|
2115
2503
|
const disabled = !aitState.state.panelEditable;
|
|
2116
2504
|
const container = h("div");
|
|
2117
2505
|
if (disabled) container.appendChild(monitoringNotice());
|
|
2118
|
-
const backBtn = h("button", { className: "ait-btn" }, "
|
|
2506
|
+
const backBtn = h("button", { className: "ait-btn" }, t("events.btn.triggerBack"));
|
|
2119
2507
|
backBtn.addEventListener("click", () => aitState.trigger("backEvent"));
|
|
2120
2508
|
if (disabled) backBtn.disabled = true;
|
|
2121
|
-
const homeBtn = h("button", { className: "ait-btn" }, "
|
|
2509
|
+
const homeBtn = h("button", { className: "ait-btn" }, t("events.btn.triggerHome"));
|
|
2122
2510
|
homeBtn.addEventListener("click", () => aitState.trigger("homeEvent"));
|
|
2123
2511
|
if (disabled) homeBtn.disabled = true;
|
|
2124
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2512
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("events.section.navigation")), h("div", { className: "ait-row" }, backBtn, homeBtn)), h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("events.section.login")), selectRow(t("events.row.loggedIn"), ["true", "false"], String(aitState.state.auth.isLoggedIn), (v) => {
|
|
2125
2513
|
aitState.patch("auth", { isLoggedIn: v === "true" });
|
|
2126
|
-
}, disabled), selectRow("
|
|
2514
|
+
}, disabled), selectRow(t("events.row.tossLoginIntegrated"), ["true", "false"], String(aitState.state.auth.isTossLoginIntegrated), (v) => {
|
|
2127
2515
|
aitState.patch("auth", { isTossLoginIntegrated: v === "true" });
|
|
2128
2516
|
}, disabled)));
|
|
2129
2517
|
return container;
|
|
@@ -2267,23 +2655,23 @@ function renderIapTab() {
|
|
|
2267
2655
|
];
|
|
2268
2656
|
if (disabled) container.appendChild(monitoringNotice());
|
|
2269
2657
|
const pendingOrders = s.iap.pendingOrders;
|
|
2270
|
-
const pendingSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" },
|
|
2271
|
-
if (pendingOrders.length === 0) pendingSection.appendChild(h("div", { className: "ait-log-entry" }, "
|
|
2658
|
+
const pendingSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("iap.section.pending", { count: pendingOrders.length })));
|
|
2659
|
+
if (pendingOrders.length === 0) pendingSection.appendChild(h("div", { className: "ait-log-entry" }, t("iap.empty.pending")));
|
|
2272
2660
|
else for (const o of pendingOrders) {
|
|
2273
|
-
const completeBtn = h("button", { className: "ait-btn ait-btn-sm" }, "
|
|
2661
|
+
const completeBtn = h("button", { className: "ait-btn ait-btn-sm" }, t("iap.btn.complete"));
|
|
2274
2662
|
if (disabled) completeBtn.disabled = true;
|
|
2275
2663
|
completeBtn.addEventListener("click", () => {
|
|
2276
2664
|
IAP.completeProductGrant({ params: { orderId: o.orderId } }).catch((err) => console.error("[@ait-co/devtools] completeProductGrant error:", err));
|
|
2277
2665
|
});
|
|
2278
|
-
pendingSection.appendChild(h("div", { className: "ait-log-entry" }, h("span", { className: "ait-log-type" }, "
|
|
2666
|
+
pendingSection.appendChild(h("div", { className: "ait-log-entry" }, h("span", { className: "ait-log-type" }, t("iap.label.pending")), `${o.sku} (${shortOrderId(o.orderId)}) · ${formatTimestamp(o.paymentCompletedDate)} `, completeBtn));
|
|
2279
2667
|
}
|
|
2280
2668
|
const completedOrders = s.iap.completedOrders;
|
|
2281
|
-
const completedSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" },
|
|
2282
|
-
if (completedOrders.length === 0) completedSection.appendChild(h("div", { className: "ait-log-entry" }, "
|
|
2669
|
+
const completedSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("iap.section.completed", { count: completedOrders.length })));
|
|
2670
|
+
if (completedOrders.length === 0) completedSection.appendChild(h("div", { className: "ait-log-entry" }, t("iap.empty.completed")));
|
|
2283
2671
|
else for (const o of completedOrders) completedSection.appendChild(h("div", { className: "ait-log-entry" }, h("span", { className: "ait-log-type" }, o.status), `${o.sku} (${shortOrderId(o.orderId)}) · ${formatTimestamp(o.date)}`));
|
|
2284
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2672
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("iap.section.simulator")), selectRow(t("iap.row.nextResult"), results, s.iap.nextResult, (v) => {
|
|
2285
2673
|
aitState.patch("iap", { nextResult: v });
|
|
2286
|
-
}, disabled)), h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2674
|
+
}, disabled)), h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("iap.section.tossPay")), selectRow(t("iap.row.tossPayResult"), ["success", "fail"], s.payment.nextResult, (v) => {
|
|
2287
2675
|
aitState.patch("payment", { nextResult: v });
|
|
2288
2676
|
}, disabled)), pendingSection, completedSection);
|
|
2289
2677
|
return container;
|
|
@@ -2295,19 +2683,19 @@ function renderLocationTab() {
|
|
|
2295
2683
|
const disabled = !s.panelEditable;
|
|
2296
2684
|
const container = h("div");
|
|
2297
2685
|
if (disabled) container.appendChild(monitoringNotice());
|
|
2298
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2686
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("location.section.current")), inputRow(t("location.row.latitude"), String(s.location.coords.latitude), (v) => {
|
|
2299
2687
|
const coords = {
|
|
2300
2688
|
...s.location.coords,
|
|
2301
2689
|
latitude: Number(v)
|
|
2302
2690
|
};
|
|
2303
2691
|
aitState.patch("location", { coords });
|
|
2304
|
-
}, disabled), inputRow("
|
|
2692
|
+
}, disabled), inputRow(t("location.row.longitude"), String(s.location.coords.longitude), (v) => {
|
|
2305
2693
|
const coords = {
|
|
2306
2694
|
...s.location.coords,
|
|
2307
2695
|
longitude: Number(v)
|
|
2308
2696
|
};
|
|
2309
2697
|
aitState.patch("location", { coords });
|
|
2310
|
-
}, disabled), inputRow("
|
|
2698
|
+
}, disabled), inputRow(t("location.row.accuracy"), String(s.location.coords.accuracy), (v) => {
|
|
2311
2699
|
const coords = {
|
|
2312
2700
|
...s.location.coords,
|
|
2313
2701
|
accuracy: Number(v)
|
|
@@ -2321,15 +2709,15 @@ function renderLocationTab() {
|
|
|
2321
2709
|
const RESULTS = [
|
|
2322
2710
|
{
|
|
2323
2711
|
value: "newAgreement",
|
|
2324
|
-
|
|
2712
|
+
labelKey: "notifications.option.newAgreement"
|
|
2325
2713
|
},
|
|
2326
2714
|
{
|
|
2327
2715
|
value: "alreadyAgreed",
|
|
2328
|
-
|
|
2716
|
+
labelKey: "notifications.option.alreadyAgreed"
|
|
2329
2717
|
},
|
|
2330
2718
|
{
|
|
2331
2719
|
value: "agreementRejected",
|
|
2332
|
-
|
|
2720
|
+
labelKey: "notifications.option.agreementRejected"
|
|
2333
2721
|
}
|
|
2334
2722
|
];
|
|
2335
2723
|
function radioRow(name, current, option, disabled) {
|
|
@@ -2343,14 +2731,14 @@ function radioRow(name, current, option, disabled) {
|
|
|
2343
2731
|
input.addEventListener("change", () => {
|
|
2344
2732
|
if (input.checked) aitState.patch("notification", { nextResult: option.value });
|
|
2345
2733
|
});
|
|
2346
|
-
return h("label", { className: "ait-row" }, input, h("span", {}, option.
|
|
2734
|
+
return h("label", { className: "ait-row" }, input, h("span", {}, t(option.labelKey)));
|
|
2347
2735
|
}
|
|
2348
2736
|
function renderNotificationsTab() {
|
|
2349
2737
|
const s = aitState.state;
|
|
2350
2738
|
const disabled = !s.panelEditable;
|
|
2351
2739
|
const container = h("div");
|
|
2352
2740
|
if (disabled) container.appendChild(monitoringNotice());
|
|
2353
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2741
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("notifications.section.title")), ...RESULTS.map((opt) => radioRow("ait-notification-result", s.notification.nextResult, opt, disabled))));
|
|
2354
2742
|
return container;
|
|
2355
2743
|
}
|
|
2356
2744
|
//#endregion
|
|
@@ -2373,7 +2761,7 @@ function renderPermissionsTab() {
|
|
|
2373
2761
|
"notDetermined"
|
|
2374
2762
|
];
|
|
2375
2763
|
if (disabled) container.appendChild(monitoringNotice());
|
|
2376
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
2764
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("permissions.section.device")), ...names.map((name) => selectRow(name, statuses, s.permissions[name], (v) => {
|
|
2377
2765
|
aitState.patch("permissions", { [name]: v });
|
|
2378
2766
|
}, disabled))));
|
|
2379
2767
|
return container;
|
|
@@ -2661,12 +3049,12 @@ function renderPresetsTab(refreshPanel) {
|
|
|
2661
3049
|
if (disabled) container.appendChild(monitoringNotice());
|
|
2662
3050
|
const userPresets = listUserPresets();
|
|
2663
3051
|
const snapshot = aitState.state;
|
|
2664
|
-
container.append(renderSection("
|
|
2665
|
-
container.append(renderSection(
|
|
2666
|
-
const saveBtn = h("button", { className: "ait-btn ait-btn-sm" }, "
|
|
3052
|
+
container.append(renderSection(t("presets.section.builtIn"), builtInPresets, disabled, snapshot, refreshPanel, false));
|
|
3053
|
+
container.append(renderSection(t("presets.section.saved", { count: userPresets.length }), userPresets, disabled, snapshot, refreshPanel, true));
|
|
3054
|
+
const saveBtn = h("button", { className: "ait-btn ait-btn-sm" }, t("presets.btn.saveCurrent"));
|
|
2667
3055
|
if (disabled) saveBtn.disabled = true;
|
|
2668
3056
|
saveBtn.addEventListener("click", () => {
|
|
2669
|
-
const label = window.prompt("
|
|
3057
|
+
const label = window.prompt(t("presets.prompt.label"));
|
|
2670
3058
|
if (label === null) return;
|
|
2671
3059
|
try {
|
|
2672
3060
|
saveUserPreset(label, captureCurrentState(aitState.state));
|
|
@@ -2676,19 +3064,19 @@ function renderPresetsTab(refreshPanel) {
|
|
|
2676
3064
|
}
|
|
2677
3065
|
refreshPanel();
|
|
2678
3066
|
});
|
|
2679
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
3067
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("presets.section.save")), h("div", { style: "color:#888;font-size:11px;margin-bottom:6px" }, t("presets.save.description")), saveBtn));
|
|
2680
3068
|
return container;
|
|
2681
3069
|
}
|
|
2682
3070
|
function renderSection(title, presets, disabled, snapshot, refreshPanel, deletable) {
|
|
2683
3071
|
const section = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, title));
|
|
2684
3072
|
if (presets.length === 0) {
|
|
2685
|
-
section.append(h("div", { style: "color:#555;font-size:12px" }, deletable ? "
|
|
3073
|
+
section.append(h("div", { style: "color:#555;font-size:12px" }, deletable ? t("presets.empty.saved") : t("presets.empty.builtIn")));
|
|
2686
3074
|
return section;
|
|
2687
3075
|
}
|
|
2688
3076
|
for (const preset of presets) {
|
|
2689
3077
|
const isActive = matchesPreset(snapshot, preset.state);
|
|
2690
3078
|
const labelEl = h("span", { className: "ait-preset-label" }, isActive ? `✓ ${preset.label}` : preset.label);
|
|
2691
|
-
const applyBtn = h("button", { className: "ait-btn ait-btn-sm" }, isActive ? "
|
|
3079
|
+
const applyBtn = h("button", { className: "ait-btn ait-btn-sm" }, isActive ? t("presets.btn.reApply") : t("presets.btn.apply"));
|
|
2692
3080
|
if (disabled) applyBtn.disabled = true;
|
|
2693
3081
|
applyBtn.addEventListener("click", () => {
|
|
2694
3082
|
applyPreset(preset.state);
|
|
@@ -2696,10 +3084,10 @@ function renderSection(title, presets, disabled, snapshot, refreshPanel, deletab
|
|
|
2696
3084
|
});
|
|
2697
3085
|
const buttons = [applyBtn];
|
|
2698
3086
|
if (deletable) {
|
|
2699
|
-
const delBtn = h("button", { className: "ait-btn ait-btn-sm ait-btn-danger" }, "
|
|
3087
|
+
const delBtn = h("button", { className: "ait-btn ait-btn-sm ait-btn-danger" }, t("presets.btn.delete"));
|
|
2700
3088
|
if (disabled) delBtn.disabled = true;
|
|
2701
3089
|
delBtn.addEventListener("click", () => {
|
|
2702
|
-
if (!window.confirm(
|
|
3090
|
+
if (!window.confirm(t("presets.confirm.delete", { label: preset.label }))) return;
|
|
2703
3091
|
deleteUserPreset(preset.id);
|
|
2704
3092
|
refreshPanel();
|
|
2705
3093
|
});
|
|
@@ -2724,13 +3112,13 @@ function renderStorageTab(refreshPanel) {
|
|
|
2724
3112
|
const key = localStorage.key(i);
|
|
2725
3113
|
if (key?.startsWith(prefix)) entries.push([key.slice(14), localStorage.getItem(key) ?? ""]);
|
|
2726
3114
|
}
|
|
2727
|
-
const clearBtn = h("button", { className: "ait-btn ait-btn-sm ait-btn-danger" }, "
|
|
3115
|
+
const clearBtn = h("button", { className: "ait-btn ait-btn-sm ait-btn-danger" }, t("storage.btn.clearAll"));
|
|
2728
3116
|
if (disabled) clearBtn.disabled = true;
|
|
2729
3117
|
clearBtn.addEventListener("click", () => {
|
|
2730
3118
|
for (const [key] of entries) localStorage.removeItem(prefix + key);
|
|
2731
3119
|
refreshPanel();
|
|
2732
3120
|
});
|
|
2733
|
-
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-row" }, h("div", { className: "ait-section-title" },
|
|
3121
|
+
container.append(h("div", { className: "ait-section" }, h("div", { className: "ait-row" }, h("div", { className: "ait-section-title" }, t("storage.section.title", { count: entries.length })), clearBtn), entries.length === 0 ? h("div", { style: "color:#555;font-size:12px" }, t("storage.empty")) : h("div", {}, ...entries.map(([key, value]) => h("div", { className: "ait-storage-row" }, h("span", { className: "ait-storage-key" }, key), h("span", { className: "ait-storage-value" }, value.length > 100 ? `${value.slice(0, 100)}...` : value))))));
|
|
2734
3122
|
return container;
|
|
2735
3123
|
}
|
|
2736
3124
|
//#endregion
|
|
@@ -2771,8 +3159,9 @@ const NONE_PRESET = {
|
|
|
2771
3159
|
};
|
|
2772
3160
|
/**
|
|
2773
3161
|
* Device presets (2026). CSS viewport 크기는 실제 기기의 `window.innerWidth/innerHeight`.
|
|
2774
|
-
* iPhone 17 시리즈는 2025-09 출시. iPhone Air
|
|
2775
|
-
*
|
|
3162
|
+
* iPhone 17 시리즈는 2025-09 출시. iPhone Air는 2026-04 기준 미출시 추정(`(est)` 라벨).
|
|
3163
|
+
* Galaxy S26 시리즈는 2026-03-11 출시 — viewport 값은 phone-simulator.com에서 보고된
|
|
3164
|
+
* 측정치를 사용. safe area는 토스 호스트 환경 실측 필요 — S25 값으로 잠정.
|
|
2776
3165
|
*
|
|
2777
3166
|
* iPhone 17과 17 Pro는 CSS viewport / DPR / safe area가 동일 — 이는 의도이며 카피-페이스트
|
|
2778
3167
|
* 실수가 아니다. Apple의 17 lineup은 base와 Pro의 web-relevant 스펙이 같다.
|
|
@@ -2841,9 +3230,9 @@ const VIEWPORT_PRESETS = [
|
|
|
2841
3230
|
},
|
|
2842
3231
|
{
|
|
2843
3232
|
id: "galaxy-s26",
|
|
2844
|
-
label: "Galaxy S26
|
|
2845
|
-
width:
|
|
2846
|
-
height:
|
|
3233
|
+
label: "Galaxy S26",
|
|
3234
|
+
width: 360,
|
|
3235
|
+
height: 773,
|
|
2847
3236
|
dpr: 3,
|
|
2848
3237
|
notch: "punch-hole-center",
|
|
2849
3238
|
safeAreaTop: 32,
|
|
@@ -2851,9 +3240,9 @@ const VIEWPORT_PRESETS = [
|
|
|
2851
3240
|
},
|
|
2852
3241
|
{
|
|
2853
3242
|
id: "galaxy-s26-plus",
|
|
2854
|
-
label: "Galaxy S26+
|
|
2855
|
-
width:
|
|
2856
|
-
height:
|
|
3243
|
+
label: "Galaxy S26+",
|
|
3244
|
+
width: 480,
|
|
3245
|
+
height: 1040,
|
|
2857
3246
|
dpr: 3,
|
|
2858
3247
|
notch: "punch-hole-center",
|
|
2859
3248
|
safeAreaTop: 32,
|
|
@@ -2861,10 +3250,10 @@ const VIEWPORT_PRESETS = [
|
|
|
2861
3250
|
},
|
|
2862
3251
|
{
|
|
2863
3252
|
id: "galaxy-s26-ultra",
|
|
2864
|
-
label: "Galaxy S26 Ultra
|
|
2865
|
-
width:
|
|
2866
|
-
height:
|
|
2867
|
-
dpr: 3
|
|
3253
|
+
label: "Galaxy S26 Ultra",
|
|
3254
|
+
width: 480,
|
|
3255
|
+
height: 1040,
|
|
3256
|
+
dpr: 3,
|
|
2868
3257
|
notch: "punch-hole-center",
|
|
2869
3258
|
safeAreaTop: 40,
|
|
2870
3259
|
safeAreaBottom: 0
|
|
@@ -3339,7 +3728,7 @@ function renderViewportTab() {
|
|
|
3339
3728
|
heightInput.value = String(clamped);
|
|
3340
3729
|
}
|
|
3341
3730
|
});
|
|
3342
|
-
customRow.append(h("div", { className: "ait-section-title" }, "
|
|
3731
|
+
customRow.append(h("div", { className: "ait-section-title" }, t("viewport.section.custom")), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.width")), widthInput), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.height")), heightInput));
|
|
3343
3732
|
}
|
|
3344
3733
|
const frameCheckbox = h("input", { type: "checkbox" });
|
|
3345
3734
|
frameCheckbox.checked = vp.frame;
|
|
@@ -3365,7 +3754,7 @@ function renderViewportTab() {
|
|
|
3365
3754
|
});
|
|
3366
3755
|
const size = resolveViewportSize(vp);
|
|
3367
3756
|
const statusEl = h("div", { className: "ait-section" });
|
|
3368
|
-
if (vp.preset === "none" || size.width === 0) statusEl.appendChild(h("div", { style: "color:#888;font-size:11px" }, "
|
|
3757
|
+
if (vp.preset === "none" || size.width === 0) statusEl.appendChild(h("div", { style: "color:#888;font-size:11px" }, t("viewport.status.noConstraint")));
|
|
3369
3758
|
else {
|
|
3370
3759
|
const preset = vp.preset === "custom" ? null : getPreset(vp.preset);
|
|
3371
3760
|
const effOrient = effectiveOrientation(vp);
|
|
@@ -3374,75 +3763,84 @@ function renderViewportTab() {
|
|
|
3374
3763
|
const dpr = preset?.dpr ?? 1;
|
|
3375
3764
|
const physW = Math.round(size.width * dpr);
|
|
3376
3765
|
const physH = Math.round(size.height * dpr);
|
|
3377
|
-
const orientDisplay = vp.orientation === "auto" ?
|
|
3378
|
-
rows.push(h("div", { className: "ait-status-row" }, h("span", {}, "
|
|
3766
|
+
const orientDisplay = vp.orientation === "auto" ? t("viewport.orientation.autoSuffix", { orient: effOrient }) : effOrient;
|
|
3767
|
+
rows.push(h("div", { className: "ait-status-row" }, h("span", {}, t("viewport.status.cssPhysical")), h("span", { className: "ait-status-value" }, `${size.width}×${size.height}@${dpr}x | ${physW}×${physH} ${orientDisplay}`)));
|
|
3379
3768
|
if (preset) {
|
|
3380
3769
|
const insets = computeSafeAreaInsets(preset, landscape, vp.landscapeSide);
|
|
3381
|
-
rows.push(h("div", { className: "ait-status-row" }, h("span", {}, "
|
|
3770
|
+
rows.push(h("div", { className: "ait-status-row" }, h("span", {}, t("viewport.status.safeArea")), h("span", { className: "ait-status-value" }, `T${insets.top} R${insets.right} B${insets.bottom} L${insets.left}`)));
|
|
3382
3771
|
}
|
|
3383
|
-
if (vp.aitNavBar && !landscape) rows.push(h("div", { className: "ait-status-row" }, h("span", {}, "
|
|
3772
|
+
if (vp.aitNavBar && !landscape) rows.push(h("div", { className: "ait-status-row" }, h("span", {}, t("viewport.status.aitNavBar")), h("span", { className: "ait-status-value" }, t("viewport.status.aitNavBarValue", {
|
|
3773
|
+
height: 48,
|
|
3774
|
+
type: vp.aitNavBarType
|
|
3775
|
+
}))));
|
|
3384
3776
|
for (const row of rows) statusEl.appendChild(row);
|
|
3385
3777
|
}
|
|
3386
|
-
const deviceSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
3778
|
+
const deviceSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("viewport.section.device")), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.preset")), presetSelect), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.orientation")), orientationSelect));
|
|
3387
3779
|
if (effectiveOrientation(vp) === "landscape" && vp.preset !== "none" && vp.preset !== "custom") {
|
|
3388
3780
|
const notch = getPreset(vp.preset).notch;
|
|
3389
|
-
if (notch === "notch" || notch === "dynamic-island") deviceSection.appendChild(h("div", { className: "ait-row" }, h("label", {}, "
|
|
3781
|
+
if (notch === "notch" || notch === "dynamic-island") deviceSection.appendChild(h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.notchSide")), landscapeSideSelect));
|
|
3390
3782
|
}
|
|
3391
|
-
container.append(deviceSection, customRow, h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, "
|
|
3783
|
+
container.append(deviceSection, customRow, h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("viewport.section.appearance")), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.showFrame")), frameCheckbox), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.showAitNavBar")), navBarCheckbox), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.navBarType")), navBarTypeSelect)), statusEl);
|
|
3392
3784
|
return container;
|
|
3393
3785
|
}
|
|
3394
3786
|
//#endregion
|
|
3395
3787
|
//#region src/panel/tabs/index.ts
|
|
3396
|
-
const
|
|
3788
|
+
const TAB_DEFS = [
|
|
3397
3789
|
{
|
|
3398
3790
|
id: "env",
|
|
3399
|
-
|
|
3791
|
+
labelKey: "panel.tab.env"
|
|
3400
3792
|
},
|
|
3401
3793
|
{
|
|
3402
3794
|
id: "presets",
|
|
3403
|
-
|
|
3795
|
+
labelKey: "panel.tab.presets"
|
|
3404
3796
|
},
|
|
3405
3797
|
{
|
|
3406
3798
|
id: "viewport",
|
|
3407
|
-
|
|
3799
|
+
labelKey: "panel.tab.viewport"
|
|
3408
3800
|
},
|
|
3409
3801
|
{
|
|
3410
3802
|
id: "permissions",
|
|
3411
|
-
|
|
3803
|
+
labelKey: "panel.tab.permissions"
|
|
3412
3804
|
},
|
|
3413
3805
|
{
|
|
3414
3806
|
id: "notifications",
|
|
3415
|
-
|
|
3807
|
+
labelKey: "panel.tab.notifications"
|
|
3416
3808
|
},
|
|
3417
3809
|
{
|
|
3418
3810
|
id: "location",
|
|
3419
|
-
|
|
3811
|
+
labelKey: "panel.tab.location"
|
|
3420
3812
|
},
|
|
3421
3813
|
{
|
|
3422
3814
|
id: "device",
|
|
3423
|
-
|
|
3815
|
+
labelKey: "panel.tab.device"
|
|
3424
3816
|
},
|
|
3425
3817
|
{
|
|
3426
3818
|
id: "iap",
|
|
3427
|
-
|
|
3819
|
+
labelKey: "panel.tab.iap"
|
|
3428
3820
|
},
|
|
3429
3821
|
{
|
|
3430
3822
|
id: "ads",
|
|
3431
|
-
|
|
3823
|
+
labelKey: "panel.tab.ads"
|
|
3432
3824
|
},
|
|
3433
3825
|
{
|
|
3434
3826
|
id: "events",
|
|
3435
|
-
|
|
3827
|
+
labelKey: "panel.tab.events"
|
|
3436
3828
|
},
|
|
3437
3829
|
{
|
|
3438
3830
|
id: "analytics",
|
|
3439
|
-
|
|
3831
|
+
labelKey: "panel.tab.analytics"
|
|
3440
3832
|
},
|
|
3441
3833
|
{
|
|
3442
3834
|
id: "storage",
|
|
3443
|
-
|
|
3835
|
+
labelKey: "panel.tab.storage"
|
|
3444
3836
|
}
|
|
3445
3837
|
];
|
|
3838
|
+
function getTabs() {
|
|
3839
|
+
return TAB_DEFS.map((def) => ({
|
|
3840
|
+
id: def.id,
|
|
3841
|
+
label: t(def.labelKey)
|
|
3842
|
+
}));
|
|
3843
|
+
}
|
|
3446
3844
|
function createTabRenderers(refreshPanel) {
|
|
3447
3845
|
return {
|
|
3448
3846
|
env: renderEnvironmentTab,
|
|
@@ -3604,6 +4002,7 @@ let injectedStyle = null;
|
|
|
3604
4002
|
let panelSwitchTabHandler = null;
|
|
3605
4003
|
let resizeHandler = null;
|
|
3606
4004
|
let aitStateUnsubscribe = null;
|
|
4005
|
+
let localeChangeHandler = null;
|
|
3607
4006
|
let tabRenderers = null;
|
|
3608
4007
|
function refreshPanel() {
|
|
3609
4008
|
if (!bodyEl || !tabsEl) return;
|
|
@@ -3613,7 +4012,7 @@ function refreshPanel() {
|
|
|
3613
4012
|
bodyEl.appendChild(tabRenderers[currentTab]());
|
|
3614
4013
|
} catch (err) {
|
|
3615
4014
|
console.error(`[@ait-co/devtools] Error rendering tab "${currentTab}":`, err);
|
|
3616
|
-
bodyEl.appendChild(h("div", { className: "ait-panel-tab-error" },
|
|
4015
|
+
bodyEl.appendChild(h("div", { className: "ait-panel-tab-error" }, t("panel.tabError", { tab: currentTab })));
|
|
3617
4016
|
}
|
|
3618
4017
|
tabsEl.querySelectorAll(".ait-panel-tab").forEach((el) => {
|
|
3619
4018
|
el.classList.toggle("active", el.getAttribute("data-tab") === currentTab);
|
|
@@ -3629,14 +4028,14 @@ function mount() {
|
|
|
3629
4028
|
document.head.appendChild(injectedStyle);
|
|
3630
4029
|
const toggle = h("button", {
|
|
3631
4030
|
className: "ait-panel-toggle",
|
|
3632
|
-
title: "
|
|
4031
|
+
title: t("panel.toggle.title")
|
|
3633
4032
|
}, "AIT");
|
|
3634
4033
|
toggleEl = toggle;
|
|
3635
4034
|
restoreButtonPosition(toggle);
|
|
3636
4035
|
panelEl = h("div", { className: "ait-panel" });
|
|
3637
4036
|
const closeBtn = h("button", {
|
|
3638
4037
|
className: "ait-panel-close",
|
|
3639
|
-
title: "
|
|
4038
|
+
title: t("panel.close")
|
|
3640
4039
|
}, "×");
|
|
3641
4040
|
closeBtn.addEventListener("click", () => {
|
|
3642
4041
|
isOpen = false;
|
|
@@ -3645,18 +4044,18 @@ function mount() {
|
|
|
3645
4044
|
});
|
|
3646
4045
|
const mockBadge = h("span", {
|
|
3647
4046
|
className: `ait-mock-badge ${aitState.state.panelEditable ? "ait-mock-badge-on" : "ait-mock-badge-off"}`,
|
|
3648
|
-
title: "
|
|
3649
|
-
}, aitState.state.panelEditable ? "
|
|
4047
|
+
title: t("panel.editMode.toggleTitle")
|
|
4048
|
+
}, aitState.state.panelEditable ? t("panel.editMode.on") : t("panel.editMode.off"));
|
|
3650
4049
|
mockBadge.addEventListener("click", () => {
|
|
3651
4050
|
aitState.update({ panelEditable: !aitState.state.panelEditable });
|
|
3652
4051
|
mockBadge.className = `ait-mock-badge ${aitState.state.panelEditable ? "ait-mock-badge-on" : "ait-mock-badge-off"}`;
|
|
3653
|
-
mockBadge.textContent = aitState.state.panelEditable ? "
|
|
4052
|
+
mockBadge.textContent = aitState.state.panelEditable ? t("panel.editMode.on") : t("panel.editMode.off");
|
|
3654
4053
|
refreshPanel();
|
|
3655
4054
|
});
|
|
3656
|
-
const headerRight = h("span", { style: "display:flex;align-items:center;gap:6px" }, mockBadge, h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v0.1.
|
|
3657
|
-
const header = h("div", { className: "ait-panel-header" }, h("span", {}, "
|
|
4055
|
+
const headerRight = h("span", { style: "display:flex;align-items:center;gap:6px" }, mockBadge, h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v0.1.19`), closeBtn);
|
|
4056
|
+
const header = h("div", { className: "ait-panel-header" }, h("span", {}, t("panel.title")), headerRight);
|
|
3658
4057
|
tabsEl = h("div", { className: "ait-panel-tabs" });
|
|
3659
|
-
for (const tab of
|
|
4058
|
+
for (const tab of getTabs()) {
|
|
3660
4059
|
const tabEl = h("button", {
|
|
3661
4060
|
className: "ait-panel-tab",
|
|
3662
4061
|
"data-tab": tab.id
|
|
@@ -3709,6 +4108,23 @@ function mount() {
|
|
|
3709
4108
|
refreshPanel();
|
|
3710
4109
|
};
|
|
3711
4110
|
window.addEventListener("__ait:panel-switch-tab", panelSwitchTabHandler);
|
|
4111
|
+
localeChangeHandler = () => {
|
|
4112
|
+
const savedTab = currentTab;
|
|
4113
|
+
const savedOpen = isOpen;
|
|
4114
|
+
disposePanel();
|
|
4115
|
+
try {
|
|
4116
|
+
mount();
|
|
4117
|
+
currentTab = savedTab;
|
|
4118
|
+
if (savedOpen && panelEl) {
|
|
4119
|
+
isOpen = true;
|
|
4120
|
+
panelEl.classList.add("open");
|
|
4121
|
+
}
|
|
4122
|
+
refreshPanel();
|
|
4123
|
+
} catch (err) {
|
|
4124
|
+
console.error("[@ait-co/devtools] Failed to re-mount after locale change:", err);
|
|
4125
|
+
}
|
|
4126
|
+
};
|
|
4127
|
+
window.addEventListener(LOCALE_CHANGE_EVENT, localeChangeHandler);
|
|
3712
4128
|
refreshPanel();
|
|
3713
4129
|
telemetry.init();
|
|
3714
4130
|
}
|
|
@@ -3724,6 +4140,7 @@ function disposePanel() {
|
|
|
3724
4140
|
if (typeof document === "undefined") return;
|
|
3725
4141
|
if (panelSwitchTabHandler && typeof window !== "undefined") window.removeEventListener("__ait:panel-switch-tab", panelSwitchTabHandler);
|
|
3726
4142
|
if (resizeHandler && typeof window !== "undefined") window.removeEventListener("resize", resizeHandler);
|
|
4143
|
+
if (localeChangeHandler && typeof window !== "undefined") window.removeEventListener(LOCALE_CHANGE_EVENT, localeChangeHandler);
|
|
3727
4144
|
if (aitStateUnsubscribe) aitStateUnsubscribe();
|
|
3728
4145
|
toggleEl?.remove();
|
|
3729
4146
|
panelEl?.remove();
|
|
@@ -3732,6 +4149,7 @@ function disposePanel() {
|
|
|
3732
4149
|
setDeviceRefreshPanel(() => {});
|
|
3733
4150
|
panelSwitchTabHandler = null;
|
|
3734
4151
|
resizeHandler = null;
|
|
4152
|
+
localeChangeHandler = null;
|
|
3735
4153
|
aitStateUnsubscribe = null;
|
|
3736
4154
|
toggleEl = null;
|
|
3737
4155
|
panelEl = null;
|