claude-code-runner 0.1.0 → 0.1.2

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/public/i18n.js ADDED
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Internationalization (i18n) module for Claude Code Runner
3
+ *
4
+ * To add a new language:
5
+ * 1. Create a new JSON file in /public/locales/ (e.g., "fr.json" for French)
6
+ * 2. Copy the structure from en.json and translate all values
7
+ * 3. The language will be automatically available in the language selector
8
+ */
9
+
10
+ const I18n = (function () {
11
+ const STORAGE_KEY = 'claude-code-runner-lang';
12
+ const DEFAULT_LOCALE = 'en';
13
+ const SUPPORTED_LOCALES = ['en', 'zh-CN'];
14
+
15
+ let currentLocale = DEFAULT_LOCALE;
16
+ let translations = {};
17
+ const loadedLocales = {};
18
+ let isInitialized = false;
19
+ let initPromise = null;
20
+
21
+ /**
22
+ * Get cookie value by name
23
+ */
24
+ function getCookie(name) {
25
+ const value = `; ${document.cookie}`;
26
+ const parts = value.split(`; ${name}=`);
27
+ if (parts.length === 2) {
28
+ return parts.pop().split(';').shift();
29
+ }
30
+ return null;
31
+ }
32
+
33
+ /**
34
+ * Set cookie with expiry
35
+ */
36
+ function setCookie(name, value, days = 365) {
37
+ const date = new Date();
38
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
39
+ const expires = `expires=${date.toUTCString()}`;
40
+ document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`;
41
+ }
42
+
43
+ /**
44
+ * Detect user's preferred language from browser settings
45
+ */
46
+ function detectBrowserLanguage() {
47
+ const browserLangs = navigator.languages || [navigator.language || navigator.userLanguage];
48
+
49
+ for (const lang of browserLangs) {
50
+ // Check exact match first
51
+ if (SUPPORTED_LOCALES.includes(lang)) {
52
+ return lang;
53
+ }
54
+ // Check language code without region (e.g., 'zh' from 'zh-TW')
55
+ const baseLang = lang.split('-')[0];
56
+ // Map common variants
57
+ if (baseLang === 'zh') {
58
+ // For Chinese, default to Simplified Chinese
59
+ return 'zh-CN';
60
+ }
61
+ if (SUPPORTED_LOCALES.includes(baseLang)) {
62
+ return baseLang;
63
+ }
64
+ }
65
+
66
+ return DEFAULT_LOCALE;
67
+ }
68
+
69
+ /**
70
+ * Load a locale's translations
71
+ */
72
+ async function loadLocale(locale) {
73
+ if (loadedLocales[locale]) {
74
+ return loadedLocales[locale];
75
+ }
76
+
77
+ try {
78
+ const response = await fetch(`/locales/${locale}.json`);
79
+ if (!response.ok) {
80
+ throw new Error(`Failed to load locale: ${locale}`);
81
+ }
82
+ const data = await response.json();
83
+ loadedLocales[locale] = data;
84
+ return data;
85
+ }
86
+ catch (error) {
87
+ console.error(`Error loading locale ${locale}:`, error);
88
+ // Fallback to English if available
89
+ if (locale !== DEFAULT_LOCALE && loadedLocales[DEFAULT_LOCALE]) {
90
+ return loadedLocales[DEFAULT_LOCALE];
91
+ }
92
+ return {};
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Get nested value from object by dot-notation path
98
+ */
99
+ function getNestedValue(obj, path) {
100
+ return path.split('.').reduce((current, key) => {
101
+ return current && current[key] !== undefined ? current[key] : null;
102
+ }, obj);
103
+ }
104
+
105
+ /**
106
+ * Translate a key
107
+ * @param {string} key - Dot-notation key (e.g., 'header.connecting')
108
+ * @param {object} params - Optional parameters for interpolation
109
+ * @returns {string} Translated string or key if not found
110
+ */
111
+ function t(key, params = {}) {
112
+ let value = getNestedValue(translations, key);
113
+
114
+ if (value === null) {
115
+ console.warn(`Translation missing for key: ${key}`);
116
+ return key;
117
+ }
118
+
119
+ // Simple interpolation: replace {{param}} with params.param
120
+ if (typeof value === 'string' && Object.keys(params).length > 0) {
121
+ Object.keys(params).forEach((param) => {
122
+ value = value.replace(new RegExp(`{{${param}}}`, 'g'), params[param]);
123
+ });
124
+ }
125
+
126
+ return value;
127
+ }
128
+
129
+ /**
130
+ * Update all elements with data-i18n attribute
131
+ */
132
+ function updateDOM() {
133
+ // Update elements with data-i18n attribute (text content)
134
+ document.querySelectorAll('[data-i18n]').forEach((element) => {
135
+ const key = element.getAttribute('data-i18n');
136
+ const translated = t(key);
137
+ if (translated !== key) {
138
+ element.textContent = translated;
139
+ }
140
+ });
141
+
142
+ // Update elements with data-i18n-placeholder attribute
143
+ document.querySelectorAll('[data-i18n-placeholder]').forEach((element) => {
144
+ const key = element.getAttribute('data-i18n-placeholder');
145
+ const translated = t(key);
146
+ if (translated !== key) {
147
+ element.placeholder = translated;
148
+ }
149
+ });
150
+
151
+ // Update elements with data-i18n-title attribute
152
+ document.querySelectorAll('[data-i18n-title]').forEach((element) => {
153
+ const key = element.getAttribute('data-i18n-title');
154
+ const translated = t(key);
155
+ if (translated !== key) {
156
+ element.title = translated;
157
+ }
158
+ });
159
+
160
+ // Update document title
161
+ document.title = t('app.title');
162
+
163
+ // Update language selector current value
164
+ const langSelector = document.getElementById('language-selector');
165
+ if (langSelector) {
166
+ langSelector.value = currentLocale;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Set the current locale
172
+ */
173
+ async function setLocale(locale) {
174
+ if (!SUPPORTED_LOCALES.includes(locale)) {
175
+ console.warn(`Locale ${locale} is not supported, falling back to ${DEFAULT_LOCALE}`);
176
+ locale = DEFAULT_LOCALE;
177
+ }
178
+
179
+ translations = await loadLocale(locale);
180
+ currentLocale = locale;
181
+ setCookie(STORAGE_KEY, locale);
182
+ document.documentElement.lang = locale;
183
+
184
+ updateDOM();
185
+
186
+ // Dispatch event for components that need to react to language changes
187
+ window.dispatchEvent(new CustomEvent('languagechange', { detail: { locale } }));
188
+ }
189
+
190
+ /**
191
+ * Initialize i18n system
192
+ */
193
+ async function init() {
194
+ if (initPromise) {
195
+ return initPromise;
196
+ }
197
+
198
+ initPromise = (async () => {
199
+ // Determine initial locale
200
+ const savedLocale = getCookie(STORAGE_KEY);
201
+ const browserLocale = detectBrowserLanguage();
202
+ const initialLocale = savedLocale || browserLocale;
203
+
204
+ // Pre-load default locale as fallback
205
+ await loadLocale(DEFAULT_LOCALE);
206
+
207
+ // Load and set the initial locale
208
+ await setLocale(initialLocale);
209
+
210
+ isInitialized = true;
211
+ })();
212
+
213
+ return initPromise;
214
+ }
215
+
216
+ /**
217
+ * Get current locale
218
+ */
219
+ function getLocale() {
220
+ return currentLocale;
221
+ }
222
+
223
+ /**
224
+ * Get list of supported locales
225
+ */
226
+ function getSupportedLocales() {
227
+ return [...SUPPORTED_LOCALES];
228
+ }
229
+
230
+ /**
231
+ * Check if i18n is initialized
232
+ */
233
+ function ready() {
234
+ return isInitialized;
235
+ }
236
+
237
+ // Public API
238
+ return {
239
+ init,
240
+ t,
241
+ setLocale,
242
+ getLocale,
243
+ getSupportedLocales,
244
+ updateDOM,
245
+ ready,
246
+ };
247
+ })();