@tuhama/translation-manager 0.6.1 → 0.7.1

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,111 @@
1
+ /**
2
+ * Service for interacting with AI-based Translation APIs (OpenAI, Gemini, etc.)
3
+ * This service is "context-aware" and uses extracted code snippets to improve translation quality.
4
+ */
5
+ class AITranslator {
6
+ constructor(config) {
7
+ this.provider = config.provider || 'openai'; // 'openai' or 'gemini'
8
+ this.apiKey = config.apiKey;
9
+ this.model = config.model || (this.provider === 'openai' ? 'gpt-4o' : 'gemini-1.5-flash');
10
+
11
+ if (!this.apiKey) {
12
+ throw new Error(`API Key is required for ${this.provider} translation.`);
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Translates text with optional context.
18
+ * @param {string|string[]} text - Text(s) to translate
19
+ * @param {string} targetLang - Target language code
20
+ * @param {string} sourceLang - Source language code
21
+ * @param {Object} context - Optional context mapping keys to snippets
22
+ */
23
+ async translate(text, targetLang, sourceLang = 'en', context = {}) {
24
+ if (!text || (Array.isArray(text) && text.length === 0)) {
25
+ return text;
26
+ }
27
+
28
+ const isArray = Array.isArray(text);
29
+ const textsToTranslate = isArray ? text : [text];
30
+
31
+ // Prepare the prompt
32
+ let prompt = `Translate the following ${sourceLang} strings to ${targetLang}.
33
+ Return only a JSON object where keys are the original strings and values are the translations.
34
+ Maintain any placeholders like {name} or {{count}}.
35
+
36
+ Strings to translate:
37
+ ${textsToTranslate.map(t => `- "${t}"`).join('\n')}
38
+ `;
39
+
40
+ // Add context if available
41
+ const contextEntries = Object.entries(context);
42
+ if (contextEntries.length > 0) {
43
+ prompt += `\nContext for some strings:\n`;
44
+ contextEntries.forEach(([key, occs]) => {
45
+ const snippets = occs.map(o => o.snippet).join('\n---\n');
46
+ prompt += `Key "${key}": used in these code locations:\n${snippets}\n`;
47
+ });
48
+ }
49
+
50
+ try {
51
+ const result = await this._callAI(prompt);
52
+ const translationsMap = JSON.parse(result);
53
+
54
+ if (isArray) {
55
+ return textsToTranslate.map(t => translationsMap[t] || t);
56
+ }
57
+ return translationsMap[textsToTranslate[0]] || textsToTranslate[0];
58
+ } catch (error) {
59
+ throw new Error(`AI Translation Error (${this.provider}): ${error.message}`);
60
+ }
61
+ }
62
+
63
+ async _callAI(prompt) {
64
+ if (this.provider === 'openai') {
65
+ return await this._callOpenAI(prompt);
66
+ } else if (this.provider === 'gemini') {
67
+ return await this._callGemini(prompt);
68
+ }
69
+ throw new Error(`Unsupported AI provider: ${this.provider}`);
70
+ }
71
+
72
+ async _callOpenAI(prompt) {
73
+ // We'll use fetch to avoid adding heavy dependencies like 'openai' package
74
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
75
+ method: 'POST',
76
+ headers: {
77
+ 'Content-Type': 'application/json',
78
+ 'Authorization': `Bearer ${this.apiKey}`
79
+ },
80
+ body: JSON.stringify({
81
+ model: this.model,
82
+ messages: [
83
+ { role: 'system', content: 'You are a professional translator for software applications.' },
84
+ { role: 'user', content: prompt }
85
+ ],
86
+ response_format: { type: 'json_object' }
87
+ })
88
+ });
89
+
90
+ const data = await response.json();
91
+ if (data.error) throw new Error(data.error.message);
92
+ return data.choices[0].message.content;
93
+ }
94
+
95
+ async _callGemini(prompt) {
96
+ const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`, {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/json' },
99
+ body: JSON.stringify({
100
+ contents: [{ parts: [{ text: prompt }] }],
101
+ generationConfig: { response_mime_type: 'application/json' }
102
+ })
103
+ });
104
+
105
+ const data = await response.json();
106
+ if (data.error) throw new Error(data.error.message);
107
+ return data.candidates[0].content.parts[0].text;
108
+ }
109
+ }
110
+
111
+ module.exports = AITranslator;
@@ -0,0 +1,40 @@
1
+ # Skill: Manage Translations
2
+
3
+ This skill enables an AI agent to efficiently manage i18n translations using the `@tuhama/translation-manager` library.
4
+
5
+ ## Context
6
+ This project uses a custom translation manager that supports AI-powered context extraction. Translation keys are stored in JSON files (usually in `src/locales` or `locales`).
7
+
8
+ ## Workflow
9
+
10
+ ### 1. Audit Translation Health
11
+ Before starting any work, always check the current status:
12
+ - **Command**: `npx @tuhama/translation-manager status`
13
+ - **Goal**: Identify missing translations, unused keys, and coverage percentage across all languages.
14
+
15
+ ### 2. Identify Missing Keys from Code
16
+ Find keys that are used in the source code (e.g., `t('key.name')`) but aren't defined in the JSON files:
17
+ - **Action**: Use the `Scanner` or the Web UI to find "Missing from files" keys.
18
+ - **AI Task**: If you find missing keys, suggest descriptive key names based on their usage.
19
+
20
+ ### 3. Generate Context-Aware Translations
21
+ When translating, leverage the extracted code context:
22
+ - **Rule**: Use the surrounding code snippets to determine the correct nuance (e.g., is "Save" a button or a noun?).
23
+ - **Style**: Maintain consistent tone across the app (default: professional and concise).
24
+ - **Placeholders**: Always preserve `{name}`, `{{count}}`, or other interpolation markers.
25
+
26
+ ### 4. Maintain Key Consistency
27
+ - **Naming**: Use dot-notation for nesting (e.g., `auth.login.button_label`).
28
+ - **Sorting**: Keys should be kept in alphabetical order. Use the "Normalize" feature or `npx @tuhama/translation-manager normalize` (if available).
29
+ - **Cleanup**: Periodically check for unused keys and remove them to keep bundle sizes small.
30
+
31
+ ## AI Guidelines for this Repo
32
+ - **Source Language**: Usually English (`en`).
33
+ - **Target Languages**: Check the `locales` directory for existing languages.
34
+ - **Naming Convention**: Prefer `snake_case` or `camelCase` based on existing patterns in the JSON files.
35
+ - **Key Discovery**: If you see hardcoded strings in `.js`, `.jsx`, `.ts`, or `.tsx` files, recommend wrapping them in `t()` and adding them to the translations.
36
+
37
+ ## Commands Reference
38
+ - `npx @tuhama/translation-manager`: Start the Web UI.
39
+ - `npx @tuhama/translation-manager status`: Get JSON health report.
40
+ - `pnpm test`: Run the test suite to ensure core logic remains robust.
@@ -1 +1 @@
1
- :root{--bg-color:#0b0e14;--sidebar-bg:#111827cc;--accent-color:#7c3aed;--accent-hover:#6d28d9;--text-color:#f3f4f6;--text-muted:#9ca3af;--border-color:#ffffff14;--glass-bg:#ffffff08;--glass-blur:blur(20px);--green:#10b981;--red:#ef4444;--transition:all .25s ease}*{box-sizing:border-box;margin:0;padding:0}body{background-color:var(--bg-color);color:var(--text-color);-webkit-font-smoothing:antialiased;font-family:Inter,system-ui,-apple-system,sans-serif;line-height:1.6}#root{height:100vh;display:flex}.app-container{flex-direction:column;width:100%;height:100vh;display:flex}.app-body{flex:1;grid-template-columns:300px 1fr;display:grid;overflow:hidden}.app-header{background:var(--sidebar-bg);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);border-bottom:1px solid var(--border-color);z-index:100;justify-content:space-between;align-items:center;min-height:70px;padding:16px 24px;display:flex}.header-left{align-items:center;display:flex}.brand-section{align-items:center;gap:16px;display:flex}.header-logo{filter:drop-shadow(0 0 8px #7c3aed66);border-radius:8px;width:36px;height:36px}.app-title{background:linear-gradient(135deg,#a78bfa 0%,#3b82f6 100%);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text;margin:0;font-size:1.4rem;font-weight:800}.header-right{align-items:center;display:flex}.header-actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.header-primary-btn{background:linear-gradient(135deg, var(--accent-color) 0%, var(--accent-hover) 100%);color:#fff;cursor:pointer;transition:var(--transition);border:none;border-radius:8px;align-items:center;gap:8px;padding:10px 16px;font-size:.9rem;font-weight:600;display:flex}.header-primary-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px #7c3aed4d}.header-btn{border:1px solid var(--border-color);color:var(--text-color);cursor:pointer;transition:var(--transition);white-space:nowrap;background:#ffffff0d;border-radius:6px;align-items:center;gap:6px;padding:8px 12px;font-size:.8rem;font-weight:500;display:flex}.header-btn:hover{border-color:var(--accent-color);color:var(--accent-color);background:#ffffff14;transform:translateY(-1px)}.sidebar{background:var(--sidebar-bg);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);border-right:1px solid var(--border-color);flex-direction:column;display:flex;overflow:hidden}.sidebar-search{border-bottom:1px solid var(--border-color);padding:20px 16px}.search-input{border:1px solid var(--border-color);color:#fff;width:100%;transition:var(--transition);background:#0003;border-radius:10px;padding:12px 16px;font-size:.95rem}.search-input:focus{border-color:var(--accent-color);outline:none;box-shadow:0 0 0 2px #7c3aed1a}.search-input::placeholder{color:var(--text-muted)}.tree-view{flex:1;padding:16px;overflow-y:auto}.loading-state{color:var(--text-muted);justify-content:center;align-items:center;padding:40px 20px;font-style:italic;display:flex}.dropdown-container{position:relative}.dropdown-menu{background:var(--sidebar-bg);border:1px solid var(--border-color);z-index:1000;-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);border-radius:8px;min-width:200px;margin-top:4px;padding:8px 0;position:absolute;top:100%;left:0;right:0;box-shadow:0 8px 32px #0000004d}.dropdown-item{width:100%;color:var(--text-color);cursor:pointer;transition:var(--transition);text-align:left;background:0 0;border:none;align-items:center;gap:12px;padding:10px 16px;font-size:.9rem;display:flex}.dropdown-item:hover{color:var(--accent-color);background:#7c3aed1a}.item-icon{text-align:center;width:16px;font-size:1rem}.dropdown-divider{background:var(--border-color);height:1px;margin:8px 16px}.dropdown-arrow{color:var(--text-muted);transition:var(--transition);font-size:.8rem}.btn-icon{font-size:1rem}.key-tree{list-style:none}.tree-node{margin-bottom:2px}.tree-children{margin:0;padding:0;list-style:none}.tree-branch-header{cursor:pointer;transition:var(--transition);color:var(--text-color);border-radius:6px;align-items:center;padding:8px 14px;font-size:.9rem;font-weight:500;display:flex}.tree-branch-header:hover{background:#ffffff0d}.tree-branch-header.has-selected-child{color:#a78bfa;background:#7c3aed14}.tree-expand-icon{color:var(--text-muted);transition:var(--transition);text-align:center;width:12px;margin-right:8px;font-size:.8rem}.tree-branch-text{flex:1;align-items:center;gap:8px;display:flex}.tree-branch-count{color:var(--text-muted);font-size:.75rem;font-weight:400}.branch-indicator{margin-left:4px;font-size:.8rem}.branch-indicator.missing{filter:drop-shadow(0 0 3px #ef44444d)}.branch-indicator.unused{opacity:.6}.tree-leaf{cursor:pointer;transition:var(--transition);color:var(--text-muted);border-radius:6px;justify-content:space-between;align-items:center;margin-bottom:2px;padding:8px 14px;font-size:.85rem;display:flex}.tree-leaf.unused{opacity:.5;font-style:italic}.tree-leaf.unused .tree-leaf-text:after{content:" (unused)";opacity:.7;margin-left:4px;font-size:.7rem}.tree-leaf-content{flex:1;align-items:center;display:flex}.tree-leaf-text{white-space:nowrap;text-overflow:ellipsis;flex:1;overflow:hidden}.tree-leaf:hover{color:#fff;background:#ffffff0d}.tree-leaf.active{color:#a78bfa;background:#7c3aed26;font-weight:600}.tree-leaf.incomplete .tree-leaf-text{color:var(--text-muted)}.missing-translation-dot{filter:drop-shadow(0 0 5px #ef444480);margin-left:8px;font-size:.8rem}.delete-icon{opacity:0;color:var(--red);cursor:pointer;transition:var(--transition);background:0 0;border:none;border-radius:4px;padding:2px 4px;font-size:1.1rem}.tree-leaf:hover .delete-icon{opacity:1}.delete-icon:hover{background:#ef44441a}.editor{justify-content:center;align-items:flex-start;padding:40px;display:flex;overflow-y:auto}.editor-form{width:100%;max-width:800px;-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);border:1px solid var(--border-color);background:#1f293766;border-radius:20px;padding:40px;box-shadow:0 25px 50px -12px #00000080}.form-group{text-align:left;margin-bottom:24px}.form-group.missing label{color:var(--red)}.form-group.missing input,.form-group.missing textarea{border-color:#ef44444d}.missing-label{margin-left:4px;font-size:.75rem;font-style:italic;font-weight:400}.form-group input,.form-group textarea{border:1px solid var(--border-color);color:#fff;background:#0000004d;border-radius:10px;width:100%;padding:14px;font-family:inherit;font-size:1rem}.form-group textarea{resize:vertical;min-height:100px}.form-actions{gap:16px;margin-top:32px;display:flex}.primary-btn{background:var(--accent-color);color:#fff;cursor:pointer;transition:var(--transition);border:none;border-radius:10px;padding:14px 28px;font-weight:600}.primary-btn:hover{background:var(--accent-hover);transform:translateY(-1px)}.secondary-btn{border:1px solid var(--border-color);color:var(--text-muted);cursor:pointer;background:0 0;border-radius:10px;padding:14px 28px;font-weight:600}.empty-state{text-align:center;max-width:500px}.hero-icon{filter:drop-shadow(0 0 20px #8b5cf64d);margin-bottom:24px;font-size:4rem}.empty-state h2{margin-bottom:12px;font-size:2rem}.empty-state p{color:var(--text-muted);margin-bottom:32px}.modal-overlay{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:1000;background:#000000b3;justify-content:center;align-items:center;padding:20px;display:flex;position:fixed;inset:0}.modal-content{border:1px solid var(--border-color);background:#1f2937;border-radius:20px;flex-direction:column;width:100%;max-width:600px;max-height:80vh;display:flex;overflow:hidden;box-shadow:0 25px 50px -12px #00000080}.modal-header{border-bottom:1px solid var(--border-color);justify-content:space-between;align-items:center;padding:24px;display:flex}.close-btn{color:var(--text-muted);cursor:pointer;transition:var(--transition);opacity:.6;background:0 0;border:none;padding:0;font-size:24px;line-height:1}.close-btn:hover{color:var(--text-color);opacity:1;transform:scale(1.1)}.modal-body{flex:1;padding:24px;overflow-y:auto}.cleanup-item{cursor:pointer;background:#ffffff08;border-radius:10px;align-items:center;gap:12px;margin-bottom:8px;padding:12px;display:flex}.cleanup-item:hover{background:#ffffff0f}.cleanup-item input{accent-color:var(--accent-color)}.maybe-used-warning{color:#f59e0b;align-items:center;gap:4px;margin-left:auto;font-size:.8rem;font-style:italic;display:flex}.modal-footer{border-top:1px solid var(--border-color);justify-content:flex-end;gap:12px;padding:20px;display:flex}.editor-header-row{justify-content:space-between;align-items:center;margin-bottom:32px;display:flex}.magic-btn{color:#a78bfa;background:#7c3aed1a;border-color:#7c3aed4d;padding:10px 16px;font-size:.85rem}.magic-btn:hover{background:var(--accent-color);color:#fff}.label-row{justify-content:space-between;align-items:center;margin-bottom:8px;display:flex}.label-row label{margin-bottom:0}.icon-btn{cursor:pointer;transition:var(--transition);background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:4px;display:flex}.icon-btn:hover{background:#ffffff1a}.magic-wand{opacity:.6;font-size:1.1rem}.form-group:hover .magic-wand{opacity:1}.alert{border-radius:10px;margin-bottom:20px;padding:12px 16px;font-size:.9rem}.alert-success{color:#34d399;background:#10b9811a;border:1px solid #10b9814d}.alert-error{color:#f87171;background:#ef44441a;border:1px solid #ef44444d}.wizard-step{margin-bottom:24px}.wizard-step label{color:var(--text-color);margin-bottom:12px;font-weight:600;display:block}.source-select{border:1px solid var(--border-color);color:#fff;background:#0000004d;border-radius:10px;width:100%;padding:12px;font-size:1rem}.report-summary{border:1px solid var(--border-color);background:#0003;border-radius:12px;padding:16px}.report-summary ul{color:var(--text-muted);margin-top:12px;padding-left:20px}.preview-container{border:1px solid var(--border-color);background:#0003;border-radius:12px;max-height:400px;padding:16px;overflow-y:auto}.lang-preview{margin-bottom:24px}.lang-preview h3{color:var(--accent-color);border-bottom:1px solid var(--border-color);margin-bottom:12px;padding-bottom:4px;font-size:.85rem}.preview-list{flex-direction:column;gap:8px;display:flex}.preview-item{gap:8px;font-size:.85rem;display:flex}.preview-key{color:var(--text-muted);font-weight:500}.preview-value{color:var(--text-color)}.settings-input{letter-spacing:2px}.help-text{color:var(--text-muted);margin-top:8px;font-size:.8rem}.help-text a{color:var(--accent-color);text-decoration:none}.help-text a:hover{text-decoration:underline}.success-text{color:var(--green);font-weight:500}.approve-btn{background:var(--green)}.approve-btn:hover{background:#059669}.tool-btn.warning{color:#f59e0b;background:#f59e0b1a;border-color:#f59e0b4d}.tool-btn.warning:hover{color:#fff;background:#f59e0b}.missing-keys-list{margin-top:16px;list-style:none}.missing-key-item{border:1px solid var(--border-color);background:#ffffff08;border-radius:12px;justify-content:space-between;align-items:center;margin-bottom:8px;padding:14px;display:flex}.key-path{color:#a78bfa;font-family:JetBrains Mono,Fira Code,monospace;font-size:.85rem}.warning-box{color:#f59e0b;background:#f59e0b14;border:1px solid #f59e0b33;border-radius:12px;margin-bottom:24px;padding:16px;font-size:.85rem}.sm{padding:8px 14px;font-size:.8rem}.tab-container{margin-top:24px}.tab-buttons{border-bottom:1px solid var(--border-color);gap:12px;margin-bottom:24px;padding-bottom:8px;display:flex}.tab-button{color:var(--text-muted);cursor:pointer;transition:var(--transition);background:0 0;border:none;border-radius:8px;padding:8px 16px;font-size:.95rem;font-weight:600;position:relative}.tab-button:hover{color:var(--text-color);background:#ffffff0d}.tab-button.active{color:var(--accent-color);background:#7c3aed1a}.tab-button.active:after{content:"";background:var(--accent-color);border-radius:2px 2px 0 0;height:2px;position:absolute;bottom:-9px;left:0;right:0}.export-tab,.import-tab{padding:8px 0}.export-info{border-left:4px solid var(--accent-color);background:#7c3aed0d;border-radius:4px 12px 12px 4px;margin-bottom:24px;padding:12px 16px}.export-info p{color:var(--text-color);margin:0;font-size:.9rem}.export-preview h3,.import-options h3{color:var(--text-color);margin-bottom:16px;font-size:1.1rem;font-weight:700}.preview-stats{grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:20px;display:grid}.preview-stats p{border:1px solid var(--border-color);background:#ffffff08;border-radius:12px;margin:0;padding:12px 16px;font-size:.9rem}.preview-sample h4{color:var(--text-muted);margin-bottom:12px;font-size:.9rem}.import-options{border-top:1px solid var(--border-color);margin-top:24px;padding-top:24px}.import-options label{cursor:pointer;color:var(--text-color);transition:var(--transition);align-items:center;gap:10px;margin-bottom:12px;font-size:.9rem;display:flex}.import-options label:hover{color:var(--accent-color)}.import-options input[type=checkbox]{accent-color:var(--accent-color);cursor:pointer;width:18px;height:18px}.export-import-modal .modal-content{max-width:600px}
1
+ :root{--bg-color:#0b0e14;--sidebar-bg:#111827cc;--accent-color:#7c3aed;--accent-hover:#6d28d9;--text-color:#f3f4f6;--text-muted:#9ca3af;--border-color:#ffffff14;--glass-bg:#ffffff08;--glass-blur:blur(20px);--green:#10b981;--red:#ef4444;--transition:all .25s ease}*{box-sizing:border-box;margin:0;padding:0}body{background-color:var(--bg-color);color:var(--text-color);-webkit-font-smoothing:antialiased;font-family:Inter,system-ui,-apple-system,sans-serif;line-height:1.6}#root{height:100vh;display:flex}.app-container{flex-direction:column;width:100%;height:100vh;display:flex}.app-body{flex:1;grid-template-columns:300px 1fr;display:grid;overflow:hidden}.app-header{background:var(--sidebar-bg);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);border-bottom:1px solid var(--border-color);z-index:100;justify-content:space-between;align-items:center;min-height:70px;padding:16px 24px;display:flex}.header-left{align-items:center;display:flex}.brand-section{align-items:center;gap:16px;display:flex}.header-logo{filter:drop-shadow(0 0 8px #7c3aed66);border-radius:8px;width:36px;height:36px}.app-title{background:linear-gradient(135deg,#a78bfa 0%,#3b82f6 100%);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text;margin:0;font-size:1.4rem;font-weight:800}.header-right{align-items:center;display:flex}.header-actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.header-primary-btn{background:linear-gradient(135deg, var(--accent-color) 0%, var(--accent-hover) 100%);color:#fff;cursor:pointer;transition:var(--transition);border:none;border-radius:8px;align-items:center;gap:8px;padding:10px 16px;font-size:.9rem;font-weight:600;display:flex}.header-primary-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px #7c3aed4d}.header-btn{border:1px solid var(--border-color);color:var(--text-color);cursor:pointer;transition:var(--transition);white-space:nowrap;background:#ffffff0d;border-radius:6px;align-items:center;gap:6px;padding:8px 12px;font-size:.8rem;font-weight:500;display:flex}.header-btn:hover{border-color:var(--accent-color);color:var(--accent-color);background:#ffffff14;transform:translateY(-1px)}.sidebar{background:var(--sidebar-bg);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);border-right:1px solid var(--border-color);flex-direction:column;display:flex;overflow:hidden}.sidebar-search{border-bottom:1px solid var(--border-color);padding:20px 16px}.search-input{border:1px solid var(--border-color);color:#fff;width:100%;transition:var(--transition);background:#0003;border-radius:10px;padding:12px 16px;font-size:.95rem}.search-input:focus{border-color:var(--accent-color);outline:none;box-shadow:0 0 0 2px #7c3aed1a}.search-input::placeholder{color:var(--text-muted)}.tree-view{flex:1;padding:16px;overflow-y:auto}.loading-state{color:var(--text-muted);justify-content:center;align-items:center;padding:40px 20px;font-style:italic;display:flex}.dropdown-container{position:relative}.dropdown-menu{background:var(--sidebar-bg);border:1px solid var(--border-color);z-index:1000;-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);border-radius:8px;min-width:200px;margin-top:4px;padding:8px 0;position:absolute;top:100%;left:0;right:0;box-shadow:0 8px 32px #0000004d}.dropdown-item{width:100%;color:var(--text-color);cursor:pointer;transition:var(--transition);text-align:left;background:0 0;border:none;align-items:center;gap:12px;padding:10px 16px;font-size:.9rem;display:flex}.dropdown-item:hover{color:var(--accent-color);background:#7c3aed1a}.item-icon{text-align:center;width:16px;font-size:1rem}.dropdown-divider{background:var(--border-color);height:1px;margin:8px 16px}.dropdown-arrow{color:var(--text-muted);transition:var(--transition);font-size:.8rem}.btn-icon{font-size:1rem}.key-tree{list-style:none}.tree-node{margin-bottom:2px}.tree-children{margin:0;padding:0;list-style:none}.tree-branch-header{cursor:pointer;transition:var(--transition);color:var(--text-color);border-radius:6px;align-items:center;padding:8px 14px;font-size:.9rem;font-weight:500;display:flex}.tree-branch-header:hover{background:#ffffff0d}.tree-branch-header.has-selected-child{color:#a78bfa;background:#7c3aed14}.tree-expand-icon{color:var(--text-muted);transition:var(--transition);text-align:center;width:12px;margin-right:8px;font-size:.8rem}.tree-branch-text{flex:1;align-items:center;gap:8px;display:flex}.tree-branch-count{color:var(--text-muted);font-size:.75rem;font-weight:400}.branch-indicator{margin-left:4px;font-size:.8rem}.branch-indicator.missing{filter:drop-shadow(0 0 3px #ef44444d)}.branch-indicator.unused{opacity:.6}.tree-leaf{cursor:pointer;transition:var(--transition);color:var(--text-muted);border-radius:6px;justify-content:space-between;align-items:center;margin-bottom:2px;padding:8px 14px;font-size:.85rem;display:flex}.tree-leaf.unused{opacity:.5;font-style:italic}.tree-leaf.unused .tree-leaf-text:after{content:" (unused)";opacity:.7;margin-left:4px;font-size:.7rem}.tree-leaf-content{flex:1;align-items:center;display:flex}.tree-leaf-text{white-space:nowrap;text-overflow:ellipsis;flex:1;overflow:hidden}.tree-leaf:hover{color:#fff;background:#ffffff0d}.tree-leaf.active{color:#a78bfa;background:#7c3aed26;font-weight:600}.tree-leaf.incomplete .tree-leaf-text{color:var(--text-muted)}.missing-translation-dot{filter:drop-shadow(0 0 5px #ef444480);margin-left:8px;font-size:.8rem}.delete-icon{opacity:0;color:var(--red);cursor:pointer;transition:var(--transition);background:0 0;border:none;border-radius:4px;padding:2px 4px;font-size:1.1rem}.tree-leaf:hover .delete-icon{opacity:1}.delete-icon:hover{background:#ef44441a}.editor{justify-content:center;align-items:flex-start;padding:40px;display:flex;overflow-y:auto}.editor-form{width:100%;max-width:800px;-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);border:1px solid var(--border-color);background:#1f293766;border-radius:20px;padding:40px;box-shadow:0 25px 50px -12px #00000080}.form-group{text-align:left;margin-bottom:24px}.form-group.missing label{color:var(--red)}.form-group.missing input,.form-group.missing textarea{border-color:#ef44444d}.missing-label{margin-left:4px;font-size:.75rem;font-style:italic;font-weight:400}.form-group input,.form-group textarea{border:1px solid var(--border-color);color:#fff;background:#0000004d;border-radius:10px;width:100%;padding:14px;font-family:inherit;font-size:1rem}.form-group textarea{resize:vertical;min-height:100px}.form-actions{gap:16px;margin-top:32px;display:flex}.primary-btn{background:var(--accent-color);color:#fff;cursor:pointer;transition:var(--transition);border:none;border-radius:10px;padding:14px 28px;font-weight:600}.primary-btn:hover{background:var(--accent-hover);transform:translateY(-1px)}.secondary-btn{border:1px solid var(--border-color);color:var(--text-muted);cursor:pointer;background:0 0;border-radius:10px;padding:14px 28px;font-weight:600}.empty-state{text-align:center;max-width:500px}.hero-icon{filter:drop-shadow(0 0 20px #8b5cf64d);margin-bottom:24px;font-size:4rem}.empty-state h2{margin-bottom:12px;font-size:2rem}.empty-state p{color:var(--text-muted);margin-bottom:32px}.modal-overlay{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:1000;background:#000000b3;justify-content:center;align-items:center;padding:20px;display:flex;position:fixed;inset:0}.modal-content{border:1px solid var(--border-color);background:#1f2937;border-radius:20px;flex-direction:column;width:100%;max-width:600px;max-height:80vh;display:flex;overflow:hidden;box-shadow:0 25px 50px -12px #00000080}.modal-header{border-bottom:1px solid var(--border-color);justify-content:space-between;align-items:center;padding:24px;display:flex}.modal-body{flex:1;padding:24px;overflow-y:auto}.cleanup-item{cursor:pointer;background:#ffffff08;border-radius:10px;align-items:center;gap:12px;margin-bottom:8px;padding:12px;display:flex}.cleanup-item:hover{background:#ffffff0f}.cleanup-item input{accent-color:var(--accent-color)}.maybe-used-warning{color:#f59e0b;align-items:center;gap:4px;margin-left:auto;font-size:.8rem;font-style:italic;display:flex}.modal-footer{border-top:1px solid var(--border-color);justify-content:flex-end;gap:12px;padding:20px;display:flex}.editor-header-row{justify-content:space-between;align-items:center;margin-bottom:32px;display:flex}.magic-btn{color:#a78bfa;background:#7c3aed1a;border-color:#7c3aed4d;padding:10px 16px;font-size:.85rem}.magic-btn:hover{background:var(--accent-color);color:#fff}.label-row{justify-content:space-between;align-items:center;margin-bottom:8px;display:flex}.label-row label{margin-bottom:0}.icon-btn{cursor:pointer;transition:var(--transition);background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:4px;display:flex}.icon-btn:hover{background:#ffffff1a}.magic-wand{opacity:.6;font-size:1.1rem}.form-group:hover .magic-wand{opacity:1}.alert{border-radius:10px;margin-bottom:20px;padding:12px 16px;font-size:.9rem}.alert-success{color:#34d399;background:#10b9811a;border:1px solid #10b9814d}.alert-error{color:#f87171;background:#ef44441a;border:1px solid #ef44444d}.wizard-step{margin-bottom:24px}.wizard-step label{color:var(--text-color);margin-bottom:12px;font-weight:600;display:block}.source-select{border:1px solid var(--border-color);color:#fff;background:#0000004d;border-radius:10px;width:100%;padding:12px;font-size:1rem}.report-summary{border:1px solid var(--border-color);background:#0003;border-radius:12px;padding:16px}.report-summary ul{color:var(--text-muted);margin-top:12px;padding-left:20px}.preview-container{border:1px solid var(--border-color);background:#0003;border-radius:12px;max-height:400px;padding:16px;overflow-y:auto}.lang-preview{margin-bottom:24px}.lang-preview h3{color:var(--accent-color);border-bottom:1px solid var(--border-color);margin-bottom:12px;padding-bottom:4px;font-size:.85rem}.preview-list{flex-direction:column;gap:8px;display:flex}.preview-item{gap:8px;font-size:.85rem;display:flex}.preview-key{color:var(--text-muted);font-weight:500}.preview-value{color:var(--text-color)}.settings-input{letter-spacing:2px}.help-text{color:var(--text-muted);margin-top:8px;font-size:.8rem}.help-text a{color:var(--accent-color);text-decoration:none}.help-text a:hover{text-decoration:underline}.success-text{color:var(--green);font-weight:500}.approve-btn{background:var(--green)}.approve-btn:hover{background:#059669}.tool-btn.warning{color:#f59e0b;background:#f59e0b1a;border-color:#f59e0b4d}.tool-btn.warning:hover{color:#fff;background:#f59e0b}.missing-keys-list{margin-top:16px;list-style:none}.missing-key-item{border:1px solid var(--border-color);background:#ffffff08;border-radius:12px;justify-content:space-between;align-items:center;margin-bottom:8px;padding:14px;display:flex}.key-path{color:#a78bfa;font-family:JetBrains Mono,Fira Code,monospace;font-size:.85rem}.warning-box{color:#f59e0b;background:#f59e0b14;border:1px solid #f59e0b33;border-radius:12px;margin-bottom:24px;padding:16px;font-size:.85rem}.sm{padding:8px 14px;font-size:.8rem}.tab-container{margin-top:24px}.tab-buttons{border-bottom:1px solid var(--border-color);gap:12px;margin-bottom:24px;padding-bottom:8px;display:flex}.tab-button{color:var(--text-muted);cursor:pointer;transition:var(--transition);background:0 0;border:none;border-radius:8px;padding:8px 16px;font-size:.95rem;font-weight:600;position:relative}.tab-button:hover{color:var(--text-color);background:#ffffff0d}.tab-button.active{color:var(--accent-color);background:#7c3aed1a}.tab-button.active:after{content:"";background:var(--accent-color);border-radius:2px 2px 0 0;height:2px;position:absolute;bottom:-9px;left:0;right:0}.export-tab,.import-tab{padding:8px 0}.export-info{border-left:4px solid var(--accent-color);background:#7c3aed0d;border-radius:4px 12px 12px 4px;margin-bottom:24px;padding:12px 16px}.export-info p{color:var(--text-color);margin:0;font-size:.9rem}.export-preview h3,.import-options h3{color:var(--text-color);margin-bottom:16px;font-size:1.1rem;font-weight:700}.preview-stats{grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:20px;display:grid}.preview-stats p{border:1px solid var(--border-color);background:#ffffff08;border-radius:12px;margin:0;padding:12px 16px;font-size:.9rem}.preview-sample h4{color:var(--text-muted);margin-bottom:12px;font-size:.9rem}.import-options{border-top:1px solid var(--border-color);margin-top:24px;padding-top:24px}.import-options label{cursor:pointer;color:var(--text-color);transition:var(--transition);align-items:center;gap:10px;margin-bottom:12px;font-size:.9rem;display:flex}.import-options label:hover{color:var(--accent-color)}.import-options input[type=checkbox]{accent-color:var(--accent-color);cursor:pointer;width:18px;height:18px}.export-import-modal .modal-content{max-width:600px}