@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.
- package/LICENSE +21 -21
- package/README.md +37 -21
- package/bin/index.js +87 -45
- package/package.json +76 -66
- package/src/core/Scanner.js +204 -128
- package/src/core/TranslatorManager.js +97 -33
- package/src/core/Utilities.js +59 -55
- package/src/core/services/AITranslator.js +111 -0
- package/translations.skill +40 -0
- package/web/dist/assets/{index-DtMLkoQW.css → index-C4pKyQzR.css} +1 -1
- package/web/dist/assets/{index-p37Bqu5O.js → index-CecH4D-L.js} +2 -2
- package/web/dist/index.html +19 -19
- package/web/package.json +29 -22
|
@@ -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}
|