@kikkimo/claude-launcher 1.0.0 → 2.0.0

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.
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Internationalization (i18n) Main Module
3
+ * Provides translation and localization services
4
+ */
5
+
6
+ const LanguageManager = require('./language-manager');
7
+ const MessageFormatter = require('./formatter');
8
+
9
+ class I18n {
10
+ constructor() {
11
+ this.languageManager = new LanguageManager();
12
+ this.formatter = MessageFormatter;
13
+ this._cache = new Map(); // Cache for translated strings
14
+ }
15
+
16
+ /**
17
+ * Translate a message key to localized text
18
+ * @param {string} key - Translation key (dot notation: 'menu.main.title')
19
+ * @param {...any} args - Arguments for placeholder formatting
20
+ * @returns {Promise<string>} - Translated and formatted text
21
+ */
22
+ async t(key, ...args) {
23
+ try {
24
+ // Check cache first for static translations (no args)
25
+ const cacheKey = args.length === 0 ? `${this.languageManager.getCurrentLanguage()}.${key}` : null;
26
+ if (cacheKey && this._cache.has(cacheKey)) {
27
+ return this._cache.get(cacheKey);
28
+ }
29
+
30
+ const keys = key.split('.');
31
+ const languagePack = await this.languageManager.getLanguagePack();
32
+
33
+ let value = languagePack;
34
+ for (const k of keys) {
35
+ value = value?.[k];
36
+ if (value === undefined) {
37
+ console.warn(`Translation key not found: ${key}`);
38
+ return key; // Return key as fallback
39
+ }
40
+ }
41
+
42
+ // If value is not a string, return the key as fallback
43
+ if (typeof value !== 'string') {
44
+ console.warn(`Translation value is not a string for key: ${key}`);
45
+ return key;
46
+ }
47
+
48
+ // Format with arguments if provided
49
+ let result = value;
50
+ if (args.length > 0) {
51
+ result = this.formatter.format(value, ...args);
52
+ } else if (cacheKey) {
53
+ // Cache static translations
54
+ this._cache.set(cacheKey, result);
55
+ }
56
+
57
+ return result;
58
+ } catch (error) {
59
+ console.warn(`Translation error for key ${key}:`, error.message);
60
+ return key; // Return key as fallback
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Get current language code
66
+ * @returns {string} - Current language code
67
+ */
68
+ getCurrentLanguage() {
69
+ return this.languageManager.getCurrentLanguage();
70
+ }
71
+
72
+ /**
73
+ * Get current language display name
74
+ * @returns {string} - Current language display name
75
+ */
76
+ getCurrentLanguageName() {
77
+ return this.languageManager.getCurrentLanguageName();
78
+ }
79
+
80
+ /**
81
+ * Get supported languages
82
+ * @returns {Object} - Object with language codes and display names
83
+ */
84
+ getSupportedLanguages() {
85
+ return this.languageManager.getSupportedLanguages();
86
+ }
87
+
88
+ /**
89
+ * Set current language
90
+ * @param {string} langCode - Language code to set
91
+ * @returns {Promise<void>}
92
+ */
93
+ async setLanguage(langCode) {
94
+ await this.languageManager.setLanguage(langCode);
95
+ this._cache.clear(); // Clear cache when language changes
96
+ }
97
+
98
+ /**
99
+ * Check if a language is supported
100
+ * @param {string} langCode - Language code to check
101
+ * @returns {boolean} - True if supported
102
+ */
103
+ isLanguageSupported(langCode) {
104
+ return this.languageManager.isLanguageSupported(langCode);
105
+ }
106
+
107
+ /**
108
+ * Clear translation cache (useful for development/testing)
109
+ */
110
+ clearCache() {
111
+ this._cache.clear();
112
+ }
113
+
114
+ /**
115
+ * Get cache statistics (for debugging)
116
+ * @returns {Object} - Cache statistics
117
+ */
118
+ getCacheStats() {
119
+ return {
120
+ size: this._cache.size,
121
+ language: this.getCurrentLanguage(),
122
+ keys: Array.from(this._cache.keys())
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Batch translate multiple keys (optimization for menu loading)
128
+ * @param {Array<string>} keys - Array of translation keys
129
+ * @returns {Promise<Array<string>>} - Array of translated strings
130
+ */
131
+ async translateBatch(keys) {
132
+ const promises = keys.map(key => this.t(key));
133
+ return Promise.all(promises);
134
+ }
135
+
136
+ /**
137
+ * Format a number according to current locale
138
+ * @param {number} number - Number to format
139
+ * @returns {string} - Formatted number
140
+ */
141
+ formatNumber(number) {
142
+ return this.formatter.formatNumber(number, this.getCurrentLanguage());
143
+ }
144
+
145
+ /**
146
+ * Format a date according to current locale
147
+ * @param {string|Date} date - Date to format
148
+ * @returns {string} - Formatted date
149
+ */
150
+ formatDate(date) {
151
+ return this.formatter.formatDate(date, this.getCurrentLanguage());
152
+ }
153
+
154
+ /**
155
+ * Get translation synchronously from cache (for performance in sync contexts)
156
+ * @param {string} key - Translation key
157
+ * @param {...any} args - Arguments for placeholder formatting
158
+ * @returns {string} - Translated text or fallback key
159
+ */
160
+ tSync(key, ...args) {
161
+ try {
162
+ const cacheKey = args.length === 0 ? `${this.languageManager.getCurrentLanguage()}.${key}` : null;
163
+ if (cacheKey && this._cache.has(cacheKey)) {
164
+ return this._cache.get(cacheKey);
165
+ }
166
+
167
+ // Try to get from current language pack synchronously
168
+ let languagePack = this.languageManager.languageCache.get(this.languageManager.getCurrentLanguage());
169
+
170
+ // If not in cache, try to load it synchronously
171
+ if (!languagePack) {
172
+ try {
173
+ const path = require('path');
174
+ languagePack = require(path.join(__dirname, 'locales', `${this.languageManager.getCurrentLanguage()}.js`));
175
+ this.languageManager.languageCache.set(this.languageManager.getCurrentLanguage(), languagePack);
176
+ } catch (loadError) {
177
+ // Fallback to English
178
+ try {
179
+ languagePack = require(path.join(__dirname, 'locales', 'en.js'));
180
+ } catch (fallbackError) {
181
+ return key;
182
+ }
183
+ }
184
+ }
185
+
186
+ const keys = key.split('.');
187
+ let value = languagePack;
188
+ for (const k of keys) {
189
+ value = value?.[k];
190
+ if (value === undefined) {
191
+ return key; // Return key as fallback
192
+ }
193
+ }
194
+
195
+ if (typeof value !== 'string' && !Array.isArray(value)) {
196
+ return key;
197
+ }
198
+
199
+ // Format with arguments if provided (only for strings)
200
+ let result = value;
201
+ if (args.length > 0 && typeof value === 'string') {
202
+ result = this.formatter.format(value, ...args);
203
+ } else if (cacheKey && typeof value === 'string') {
204
+ // Cache static translations (only for strings)
205
+ this._cache.set(cacheKey, result);
206
+ }
207
+
208
+ return result;
209
+ } catch (error) {
210
+ return key; // Return key as fallback
211
+ }
212
+ }
213
+ }
214
+
215
+ // Create global singleton instance
216
+ const i18n = new I18n();
217
+
218
+ module.exports = i18n;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Language Manager Module
3
+ * Manages language preferences and loading
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ class LanguageManager {
11
+ constructor() {
12
+ this.currentLanguage = 'en';
13
+ this.supportedLanguages = {
14
+ 'zh': '简体中文',
15
+ 'zh-TW': '繁體中文',
16
+ 'en': 'English',
17
+ 'ja': '日本語',
18
+ 'ko': '한국어',
19
+ 'de': 'Deutsch',
20
+ 'fr': 'Français',
21
+ 'es': 'Español',
22
+ 'ru': 'Русский',
23
+ 'it': 'Italiano',
24
+ 'pt': 'Português'
25
+ };
26
+ this.languageCache = new Map();
27
+ this.configFile = path.join(os.homedir(), '.claude-launcher-config.json');
28
+ this.loadLanguagePreference();
29
+ }
30
+
31
+ /**
32
+ * Get list of supported languages
33
+ * @returns {Object} - Object with language codes as keys and names as values
34
+ */
35
+ getSupportedLanguages() {
36
+ return { ...this.supportedLanguages };
37
+ }
38
+
39
+ /**
40
+ * Get current language code
41
+ * @returns {string} - Current language code
42
+ */
43
+ getCurrentLanguage() {
44
+ return this.currentLanguage;
45
+ }
46
+
47
+ /**
48
+ * Get current language display name
49
+ * @returns {string} - Current language display name
50
+ */
51
+ getCurrentLanguageName() {
52
+ return this.supportedLanguages[this.currentLanguage] || 'Unknown';
53
+ }
54
+
55
+ /**
56
+ * Set current language
57
+ * @param {string} langCode - Language code to set
58
+ * @throws {Error} - If language is not supported
59
+ */
60
+ async setLanguage(langCode) {
61
+ if (!this.supportedLanguages[langCode]) {
62
+ throw new Error(`Unsupported language: ${langCode}`);
63
+ }
64
+
65
+ this.currentLanguage = langCode;
66
+ await this.saveLanguagePreference();
67
+
68
+ // Clear cache to force reload
69
+ this.languageCache.clear();
70
+ }
71
+
72
+ /**
73
+ * Get language pack for current language
74
+ * @returns {Object} - Language pack object
75
+ */
76
+ async getLanguagePack() {
77
+ if (this.languageCache.has(this.currentLanguage)) {
78
+ return this.languageCache.get(this.currentLanguage);
79
+ }
80
+
81
+ try {
82
+ const languagePack = require(path.join(__dirname, 'locales', `${this.currentLanguage}.js`));
83
+ this.languageCache.set(this.currentLanguage, languagePack);
84
+ return languagePack;
85
+ } catch (error) {
86
+ console.warn(`Failed to load language pack for ${this.currentLanguage}, falling back to English. Error: ${error.message}`);
87
+
88
+ // Fallback to English
89
+ if (this.currentLanguage !== 'en') {
90
+ const englishPack = require(path.join(__dirname, 'locales', 'en.js'));
91
+ return englishPack;
92
+ }
93
+
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Save language preference to config file
100
+ */
101
+ async saveLanguagePreference() {
102
+ try {
103
+ let config = {};
104
+
105
+ // Load existing config to preserve other settings
106
+ if (fs.existsSync(this.configFile)) {
107
+ try {
108
+ config = JSON.parse(fs.readFileSync(this.configFile, 'utf8'));
109
+ } catch (e) {
110
+ // If parse fails, start fresh
111
+ config = {};
112
+ }
113
+ }
114
+
115
+ // Update language setting
116
+ config.language = this.currentLanguage;
117
+ config.lastUpdated = new Date().toISOString();
118
+
119
+ fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2));
120
+ } catch (error) {
121
+ console.warn('Failed to save language preference:', error.message);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Load language preference from config file
127
+ */
128
+ loadLanguagePreference() {
129
+ try {
130
+ if (fs.existsSync(this.configFile)) {
131
+ const config = JSON.parse(fs.readFileSync(this.configFile, 'utf8'));
132
+ if (config.language && this.supportedLanguages[config.language]) {
133
+ this.currentLanguage = config.language;
134
+ }
135
+ }
136
+ } catch (error) {
137
+ console.warn('Failed to load language preference, using default (English)');
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Check if a language is supported
143
+ * @param {string} langCode - Language code to check
144
+ * @returns {boolean} - True if supported
145
+ */
146
+ isLanguageSupported(langCode) {
147
+ return !!this.supportedLanguages[langCode];
148
+ }
149
+
150
+ /**
151
+ * Add support for a new language (for future extension)
152
+ * @param {string} langCode - Language code
153
+ * @param {string} displayName - Display name for the language
154
+ */
155
+ addLanguageSupport(langCode, displayName) {
156
+ this.supportedLanguages[langCode] = displayName;
157
+ }
158
+ }
159
+
160
+ module.exports = LanguageManager;