addonova 1.0.3 → 1.0.4
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 +17 -4
- package/package.json +3 -2
- package/src/build/bundle-html.js +105 -76
- package/src/cli/help.js +1 -0
- package/src/cli/index.js +4 -0
- package/src/commands/tool.js +32 -0
- package/src/tools/tools-server.js +287 -0
- package/src/tools/tools.html +388 -0
- package/templates/extension/config/firefox.js +5 -4
- package/templates/extension/config/thunderbird.js +5 -4
- package/templates/extension/package.json.tpl +3 -2
- package/templates/extension/platform/naver/platform.js +0 -15
- package/src/tools/json2i18n.js +0 -37
- package/src/tools/translate.js +0 -299
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Addonova Tools</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
body { font-family: system-ui, -apple-system, sans-serif; background: #f5f5f5; color: #222; min-height: 100vh; }
|
|
10
|
+
.header { background: #1a73e8; color: #fff; padding: 16px 24px; display: flex; align-items: center; gap: 12px; }
|
|
11
|
+
.header h1 { font-size: 20px; font-weight: 600; }
|
|
12
|
+
.header span { font-size: 13px; opacity: 0.85; }
|
|
13
|
+
.tabs { display: flex; background: #e8e8e8; border-bottom: 1px solid #ccc; }
|
|
14
|
+
.tab { padding: 10px 24px; cursor: pointer; border: none; background: none; font-size: 14px; font-weight: 500; color: #555; border-bottom: 2px solid transparent; }
|
|
15
|
+
.tab:hover { background: #ddd; }
|
|
16
|
+
.tab.active { background: #fff; color: #1a73e8; border-bottom-color: #1a73e8; }
|
|
17
|
+
.container { max-width: 1100px; margin: 0 auto; padding: 24px; display: none; }
|
|
18
|
+
.container.active { display: block; }
|
|
19
|
+
.card { background: #fff; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,.1); padding: 20px; margin-bottom: 16px; }
|
|
20
|
+
.card h2 { font-size: 16px; margin-bottom: 12px; color: #333; }
|
|
21
|
+
.form-row { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; align-items: flex-start; }
|
|
22
|
+
label { font-size: 13px; font-weight: 500; color: #555; display: block; margin-bottom: 4px; }
|
|
23
|
+
input[type=text], textarea, select { width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px; font-family: inherit; }
|
|
24
|
+
input[type=text]:focus, textarea:focus, select:focus { outline: none; border-color: #1a73e8; box-shadow: 0 0 0 2px rgba(26,115,232,.2); }
|
|
25
|
+
textarea { min-height: 80px; resize: vertical; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 13px; }
|
|
26
|
+
button { padding: 8px 16px; border: none; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; background: #1a73e8; color: #fff; }
|
|
27
|
+
button:hover { background: #1557b0; }
|
|
28
|
+
button:disabled { opacity: .5; cursor: not-allowed; }
|
|
29
|
+
button.danger { background: #d93025; }
|
|
30
|
+
button.danger:hover { background: #b3261e; }
|
|
31
|
+
button.outline { background: transparent; border: 1px solid #ccc; color: #333; }
|
|
32
|
+
button.outline:hover { background: #f0f0f0; }
|
|
33
|
+
.locale-list { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
|
|
34
|
+
.locale-tag { padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; background: #e8f0fe; color: #1a73e8; }
|
|
35
|
+
.msg-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
36
|
+
.msg-table th, .msg-table td { padding: 6px 10px; text-align: left; border-bottom: 1px solid #eee; }
|
|
37
|
+
.msg-table th { font-weight: 600; color: #555; position: sticky; top: 0; background: #fafafa; }
|
|
38
|
+
.msg-table tr:hover td { background: #f8f9ff; }
|
|
39
|
+
.msg-table .key { font-family: monospace; font-weight: 600; color: #1a73e8; white-space: nowrap; }
|
|
40
|
+
.msg-table .val { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
41
|
+
.msg-table .del-btn { color: #d93025; cursor: pointer; font-size: 12px; padding: 2px 6px; border-radius: 4px; }
|
|
42
|
+
.msg-table .del-btn:hover { background: #fce8e6; }
|
|
43
|
+
.file-drop { border: 2px dashed #ccc; border-radius: 8px; padding: 32px; text-align: center; cursor: pointer; transition: .2s; }
|
|
44
|
+
.file-drop:hover, .file-drop.dragover { border-color: #1a73e8; background: #f0f6ff; }
|
|
45
|
+
.file-drop p { color: #888; font-size: 14px; }
|
|
46
|
+
.file-drop .icon { font-size: 36px; margin-bottom: 8px; }
|
|
47
|
+
.output-area { background: #fafafa; border: 1px solid #eee; border-radius: 6px; padding: 12px; font-family: monospace; font-size: 13px; white-space: pre-wrap; max-height: 300px; overflow: auto; margin-top: 10px; }
|
|
48
|
+
.loading { opacity: .6; pointer-events: none; }
|
|
49
|
+
.spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid #ccc; border-top-color: #1a73e8; border-radius: 50%; animation: spin .6s linear infinite; margin-right: 6px; vertical-align: middle; }
|
|
50
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
51
|
+
.toast { position: fixed; bottom: 20px; right: 20px; padding: 10px 16px; border-radius: 6px; color: #fff; font-size: 13px; opacity: 0; transition: .3s; z-index: 100; }
|
|
52
|
+
.toast.show { opacity: 1; }
|
|
53
|
+
.toast.ok { background: #188038; }
|
|
54
|
+
.toast.err { background: #d93025; }
|
|
55
|
+
.col { flex: 1; min-width: 200px; }
|
|
56
|
+
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
57
|
+
@media (max-width: 700px) { .grid2 { grid-template-columns: 1fr; } }
|
|
58
|
+
</style>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
|
|
62
|
+
<div class="header">
|
|
63
|
+
<h1>🛠 Addonova Tools</h1>
|
|
64
|
+
<span id="status">disconnected</span>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class="tabs">
|
|
68
|
+
<button class="tab active" data-tab="translate">Translate</button>
|
|
69
|
+
<button class="tab" data-tab="json2i18n">JSON → i18n</button>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="container active" id="tab-translate">
|
|
73
|
+
<div class="card">
|
|
74
|
+
<h2>Locales</h2>
|
|
75
|
+
<div class="locale-list" id="localeList"><span class="spinner"></span> loading...</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="grid2">
|
|
79
|
+
<div class="card">
|
|
80
|
+
<h2>Add Message</h2>
|
|
81
|
+
<div class="form-row">
|
|
82
|
+
<div class="col">
|
|
83
|
+
<label>English text</label>
|
|
84
|
+
<input type="text" id="addMsgText" placeholder="Enter English message">
|
|
85
|
+
</div>
|
|
86
|
+
<div class="col">
|
|
87
|
+
<label>Message key (leave blank for auto)</label>
|
|
88
|
+
<input type="text" id="addMsgKey" placeholder="auto-generated">
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<button id="addMsgBtn">Add & Translate</button>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="card">
|
|
95
|
+
<h2>Delete Message</h2>
|
|
96
|
+
<div class="form-row">
|
|
97
|
+
<div class="col">
|
|
98
|
+
<label>Message key</label>
|
|
99
|
+
<input type="text" id="delMsgKey" placeholder="message_key">
|
|
100
|
+
</div>
|
|
101
|
+
<div class="col">
|
|
102
|
+
<label>Scope</label>
|
|
103
|
+
<select id="delMsgScope">
|
|
104
|
+
<option value="all">All locales</option>
|
|
105
|
+
</select>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
<button class="danger" id="delMsgBtn">Delete</button>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div class="card">
|
|
113
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
|
114
|
+
<h2 style="margin:0">Messages</h2>
|
|
115
|
+
<button class="outline" id="refreshMsgBtn" style="font-size:12px">Refresh</button>
|
|
116
|
+
</div>
|
|
117
|
+
<div id="msgViewer" style="max-height:400px;overflow:auto">
|
|
118
|
+
<div class="spinner"></div> loading...
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div class="container" id="tab-json2i18n">
|
|
124
|
+
<div class="grid2">
|
|
125
|
+
<div class="card">
|
|
126
|
+
<h2>Upload messages.json</h2>
|
|
127
|
+
<div class="file-drop" id="fileDrop">
|
|
128
|
+
<div class="icon">📄</div>
|
|
129
|
+
<p>Drop a <strong>messages.json</strong> file here or click to browse</p>
|
|
130
|
+
</div>
|
|
131
|
+
<input type="file" id="fileInput" accept=".json" style="display:none">
|
|
132
|
+
<div id="jsonPreview" class="output-area" style="display:none;margin-top:12px"></div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div class="card">
|
|
136
|
+
<h2>i18n Output</h2>
|
|
137
|
+
<button id="copyI18nBtn" style="margin-bottom:8px;font-size:12px" class="outline">📋 Copy</button>
|
|
138
|
+
<div id="i18nOutput" class="output-area">Upload a file to see the result</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="toast" id="toast"></div>
|
|
144
|
+
|
|
145
|
+
<script>
|
|
146
|
+
const api = (path, opts = {}) =>
|
|
147
|
+
fetch(path, { headers: { 'Content-Type': 'application/json' }, ...opts })
|
|
148
|
+
.then(r => r.json())
|
|
149
|
+
.catch(() => ({ error: 'Network error' }));
|
|
150
|
+
|
|
151
|
+
function toast(msg, type = 'ok') {
|
|
152
|
+
const el = document.getElementById('toast');
|
|
153
|
+
el.textContent = msg;
|
|
154
|
+
el.className = `toast ${type} show`;
|
|
155
|
+
clearTimeout(el._t);
|
|
156
|
+
el._t = setTimeout(() => el.classList.remove('show'), 3000);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── Tabs ──
|
|
160
|
+
document.querySelectorAll('.tab').forEach(tab => {
|
|
161
|
+
tab.addEventListener('click', () => {
|
|
162
|
+
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
163
|
+
document.querySelectorAll('.container').forEach(c => c.classList.remove('active'));
|
|
164
|
+
tab.classList.add('active');
|
|
165
|
+
document.getElementById('tab-' + tab.dataset.tab).classList.add('active');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ── Locales ──
|
|
170
|
+
async function loadLocales() {
|
|
171
|
+
const data = await api('/api/locales');
|
|
172
|
+
const list = document.getElementById('localeList');
|
|
173
|
+
const scope = document.getElementById('delMsgScope');
|
|
174
|
+
|
|
175
|
+
if (data.error) {
|
|
176
|
+
list.innerHTML = `<span style="color:#d93025">⚠ ${data.error}</span>`;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
list.innerHTML = (data.locales || []).map(l => `<span class="locale-tag">${l}</span>`).join('');
|
|
181
|
+
|
|
182
|
+
const current = scope.value;
|
|
183
|
+
scope.innerHTML = '<option value="all">All locales</option>'
|
|
184
|
+
+ (data.locales || []).map(l => `<option value="${l}">${l}</option>`).join('');
|
|
185
|
+
if (current) scope.value = current;
|
|
186
|
+
|
|
187
|
+
return data;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Messages table ──
|
|
191
|
+
async function loadMessages() {
|
|
192
|
+
const el = document.getElementById('msgViewer');
|
|
193
|
+
const data = await api('/api/locales');
|
|
194
|
+
|
|
195
|
+
if (data.error) {
|
|
196
|
+
el.innerHTML = `<span style="color:#d93025">⚠ ${data.error}</span>`;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!data.locales || !data.locales.length) {
|
|
201
|
+
el.innerHTML = '<span style="color:#888">No locale files found in src/_locales/</span>';
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const en = data.data['en'] || {};
|
|
206
|
+
const keys = Object.keys(en);
|
|
207
|
+
|
|
208
|
+
if (!keys.length) {
|
|
209
|
+
el.innerHTML = '<span style="color:#888">No messages found</span>';
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let html = '<table class="msg-table"><thead><tr><th>Key</th><th>English</th>';
|
|
214
|
+
for (const loc of data.locales) html += `<th>${loc}</th>`;
|
|
215
|
+
html += '<th></th></tr></thead><tbody>';
|
|
216
|
+
|
|
217
|
+
for (const key of keys) {
|
|
218
|
+
html += `<tr><td class="key">${key}</td>`;
|
|
219
|
+
html += `<td class="val">${esc(data.data.en[key] || '')}</td>`;
|
|
220
|
+
for (const loc of data.locales) {
|
|
221
|
+
html += `<td class="val">${esc(data.data[loc]?.[key] || '')}</td>`;
|
|
222
|
+
}
|
|
223
|
+
html += `<td><span class="del-btn" data-key="${key}">✕</span></td>`;
|
|
224
|
+
html += '</tr>';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
html += '</tbody></table>';
|
|
228
|
+
el.innerHTML = html;
|
|
229
|
+
|
|
230
|
+
el.querySelectorAll('.del-btn').forEach(btn => {
|
|
231
|
+
btn.addEventListener('click', async () => {
|
|
232
|
+
const key = btn.dataset.key;
|
|
233
|
+
if (!confirm(`Delete "${key}" from all locales?`)) return;
|
|
234
|
+
const res = await api('/api/messages/delete', {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
body: JSON.stringify({ messageId: key }),
|
|
237
|
+
});
|
|
238
|
+
if (res.error) { toast(res.error, 'err'); return; }
|
|
239
|
+
toast(`Deleted from ${res.deleted.length} locale(s)`);
|
|
240
|
+
loadMessages();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function esc(s) {
|
|
246
|
+
const d = document.createElement('div');
|
|
247
|
+
d.textContent = s;
|
|
248
|
+
return d.innerHTML;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ── Add message ──
|
|
252
|
+
document.getElementById('addMsgBtn').addEventListener('click', async function () {
|
|
253
|
+
const message = document.getElementById('addMsgText').value.trim();
|
|
254
|
+
if (!message) { toast('Enter a message', 'err'); return; }
|
|
255
|
+
const customId = document.getElementById('addMsgKey').value.trim();
|
|
256
|
+
|
|
257
|
+
this.disabled = true;
|
|
258
|
+
this.innerHTML = '<span class="spinner"></span> Translating...';
|
|
259
|
+
|
|
260
|
+
const res = await api('/api/messages/add', {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
body: JSON.stringify({ message, customId: customId || undefined }),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
this.disabled = false;
|
|
266
|
+
this.textContent = 'Add & Translate';
|
|
267
|
+
|
|
268
|
+
if (res.error) { toast(res.error, 'err'); return; }
|
|
269
|
+
toast(`Added as "${res.messageId}" — translated to ${Object.keys(res.locales).length} locales`);
|
|
270
|
+
document.getElementById('addMsgText').value = '';
|
|
271
|
+
document.getElementById('addMsgKey').value = '';
|
|
272
|
+
loadMessages();
|
|
273
|
+
loadLocales();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ── Delete message ──
|
|
277
|
+
document.getElementById('delMsgBtn').addEventListener('click', async function () {
|
|
278
|
+
const messageId = document.getElementById('delMsgKey').value.trim();
|
|
279
|
+
if (!messageId) { toast('Enter message key', 'err'); return; }
|
|
280
|
+
|
|
281
|
+
const scope = document.getElementById('delMsgScope').value;
|
|
282
|
+
const targetLocale = scope === 'all' ? null : scope;
|
|
283
|
+
const label = targetLocale ? `"${targetLocale}"` : 'ALL locales';
|
|
284
|
+
|
|
285
|
+
if (!confirm(`Delete "${messageId}" from ${label}?`)) return;
|
|
286
|
+
|
|
287
|
+
this.disabled = true;
|
|
288
|
+
this.innerHTML = 'Deleting...';
|
|
289
|
+
|
|
290
|
+
const res = await api('/api/messages/delete', {
|
|
291
|
+
method: 'POST',
|
|
292
|
+
body: JSON.stringify({ messageId, targetLocale }),
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
this.disabled = false;
|
|
296
|
+
this.textContent = 'Delete';
|
|
297
|
+
|
|
298
|
+
if (res.error) { toast(res.error, 'err'); return; }
|
|
299
|
+
toast(`Deleted from ${res.deleted.length} locale(s)`);
|
|
300
|
+
document.getElementById('delMsgKey').value = '';
|
|
301
|
+
loadMessages();
|
|
302
|
+
loadLocales();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// ── Refresh ──
|
|
306
|
+
document.getElementById('refreshMsgBtn').addEventListener('click', () => {
|
|
307
|
+
loadMessages();
|
|
308
|
+
loadLocales();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// ── JSON → i18n ──
|
|
312
|
+
const fileDrop = document.getElementById('fileDrop');
|
|
313
|
+
const fileInput = document.getElementById('fileInput');
|
|
314
|
+
const jsonPreview = document.getElementById('jsonPreview');
|
|
315
|
+
const i18nOutput = document.getElementById('i18nOutput');
|
|
316
|
+
|
|
317
|
+
fileDrop.addEventListener('click', () => fileInput.click());
|
|
318
|
+
|
|
319
|
+
fileDrop.addEventListener('dragover', e => {
|
|
320
|
+
e.preventDefault();
|
|
321
|
+
fileDrop.classList.add('dragover');
|
|
322
|
+
});
|
|
323
|
+
fileDrop.addEventListener('dragleave', () => fileDrop.classList.remove('dragover'));
|
|
324
|
+
fileDrop.addEventListener('drop', e => {
|
|
325
|
+
e.preventDefault();
|
|
326
|
+
fileDrop.classList.remove('dragover');
|
|
327
|
+
if (e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
fileInput.addEventListener('change', () => {
|
|
331
|
+
if (fileInput.files.length) handleFile(fileInput.files[0]);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
function handleFile(file) {
|
|
335
|
+
if (!file.name.endsWith('.json')) {
|
|
336
|
+
toast('Please select a .json file', 'err');
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const reader = new FileReader();
|
|
341
|
+
reader.onload = async (e) => {
|
|
342
|
+
try {
|
|
343
|
+
const json = JSON.parse(e.target.result);
|
|
344
|
+
jsonPreview.textContent = JSON.stringify(json, null, 2);
|
|
345
|
+
jsonPreview.style.display = 'block';
|
|
346
|
+
|
|
347
|
+
const res = await api('/api/json2i18n', {
|
|
348
|
+
method: 'POST',
|
|
349
|
+
body: JSON.stringify({ json }),
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (res.error) { toast(res.error, 'err'); return; }
|
|
353
|
+
i18nOutput.textContent = res.i18n;
|
|
354
|
+
} catch {
|
|
355
|
+
toast('Invalid JSON file', 'err');
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
reader.readAsText(file);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
document.getElementById('copyI18nBtn').addEventListener('click', () => {
|
|
362
|
+
const text = i18nOutput.textContent;
|
|
363
|
+
if (!text || text === 'Upload a file to see the result') return;
|
|
364
|
+
navigator.clipboard.writeText(text).then(() => toast('Copied!'));
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// ── Status ──
|
|
368
|
+
(async function init() {
|
|
369
|
+
const data = await api('/api/locales');
|
|
370
|
+
const status = document.getElementById('status');
|
|
371
|
+
if (data.error) {
|
|
372
|
+
status.textContent = '⚠ server error';
|
|
373
|
+
status.style.color = '#ffbb00';
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
status.textContent = `✔ ${data.locales.length} locales`;
|
|
377
|
+
status.style.color = '#a8e6a3';
|
|
378
|
+
|
|
379
|
+
if (data.locales.length) {
|
|
380
|
+
loadMessages();
|
|
381
|
+
} else {
|
|
382
|
+
document.getElementById('msgViewer').innerHTML =
|
|
383
|
+
'<span style="color:#888">No locale files found in src/_locales/</span>';
|
|
384
|
+
}
|
|
385
|
+
})();
|
|
386
|
+
</script>
|
|
387
|
+
</body>
|
|
388
|
+
</html>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
js: {
|
|
3
3
|
entry: {
|
|
4
|
-
'': ['src/js/background.js'
|
|
4
|
+
'': ['src/js/background.js'],
|
|
5
|
+
'data/interface': ['src/js/popup.js'],
|
|
5
6
|
},
|
|
6
7
|
filename: '[name]',
|
|
7
8
|
minify: true,
|
|
@@ -9,7 +10,7 @@ export default {
|
|
|
9
10
|
},
|
|
10
11
|
css: {
|
|
11
12
|
entry: {
|
|
12
|
-
'': ['src/css/popup.css'],
|
|
13
|
+
'data/interface': ['src/css/popup.css'],
|
|
13
14
|
},
|
|
14
15
|
filename: '[name]',
|
|
15
16
|
minify: true,
|
|
@@ -17,13 +18,13 @@ export default {
|
|
|
17
18
|
},
|
|
18
19
|
html: {
|
|
19
20
|
entry: {
|
|
20
|
-
'': ['src/html/popup.html'],
|
|
21
|
+
'data/interface': ['src/html/popup.html'],
|
|
21
22
|
},
|
|
22
23
|
filename: '[name]',
|
|
23
24
|
},
|
|
24
25
|
assets: {
|
|
25
26
|
entry: {
|
|
26
|
-
'': ['src/assets
|
|
27
|
+
'data/icons': ['src/assets/icons'],
|
|
27
28
|
},
|
|
28
29
|
},
|
|
29
30
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
js: {
|
|
3
3
|
entry: {
|
|
4
|
-
'': ['src/js/background.js'
|
|
4
|
+
'': ['src/js/background.js'],
|
|
5
|
+
'data/interface': ['src/js/popup.js'],
|
|
5
6
|
},
|
|
6
7
|
filename: '[name]',
|
|
7
8
|
minify: true,
|
|
@@ -9,7 +10,7 @@ export default {
|
|
|
9
10
|
},
|
|
10
11
|
css: {
|
|
11
12
|
entry: {
|
|
12
|
-
'': ['src/css/popup.css'],
|
|
13
|
+
'data/interface': ['src/css/popup.css'],
|
|
13
14
|
},
|
|
14
15
|
filename: '[name]',
|
|
15
16
|
minify: true,
|
|
@@ -17,13 +18,13 @@ export default {
|
|
|
17
18
|
},
|
|
18
19
|
html: {
|
|
19
20
|
entry: {
|
|
20
|
-
'': ['src/html/popup.html'],
|
|
21
|
+
'data/interface': ['src/html/popup.html'],
|
|
21
22
|
},
|
|
22
23
|
filename: '[name]',
|
|
23
24
|
},
|
|
24
25
|
assets: {
|
|
25
26
|
entry: {
|
|
26
|
-
'': ['src/assets
|
|
27
|
+
'data/icons': ['src/assets/icons'],
|
|
27
28
|
},
|
|
28
29
|
},
|
|
29
30
|
};
|
|
@@ -25,11 +25,12 @@
|
|
|
25
25
|
"debug:firefox": "addonova build --firefox --debug",
|
|
26
26
|
"debug:thunderbird": "addonova build --thunderbird --debug",
|
|
27
27
|
"debug:naver": "addonova build --naver --debug",
|
|
28
|
-
"dev": "addonova build --all --debug --watch
|
|
28
|
+
"dev": "addonova build --all --debug --watch",
|
|
29
|
+
"tool": "addonova tool",
|
|
29
30
|
"zip": "addonova zip",
|
|
30
31
|
"test": "node --test"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"addonova": "^1.0.
|
|
34
|
+
"addonova": "^1.0.4"
|
|
34
35
|
}
|
|
35
36
|
}
|
|
@@ -10,19 +10,4 @@ const LINKS = {
|
|
|
10
10
|
"facebook": "https://www.facebook.com/codehemu/",
|
|
11
11
|
"youtube": "https://www.youtube.com/@CodeHemu",
|
|
12
12
|
"twitter": "https://x.com/CodeHemu"
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const DEBOUNCE = (callback, delay) => {
|
|
16
|
-
if (typeof callback !== "function") {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
let timer;
|
|
21
|
-
return function(...args) {
|
|
22
|
-
const context = this;
|
|
23
|
-
clearTimeout(timer);
|
|
24
|
-
timer = setTimeout(function() {
|
|
25
|
-
callback.apply(context, args);
|
|
26
|
-
}, delay);
|
|
27
|
-
};
|
|
28
13
|
};
|
package/src/tools/json2i18n.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readdirSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { join, dirname } from "path";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
|
|
5
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
-
const __dirname = dirname(__filename);
|
|
7
|
-
|
|
8
|
-
const localesDir = join(__dirname, "../", "_locales");
|
|
9
|
-
const outputDir = join(__dirname, "../", "output", "_locales");
|
|
10
|
-
|
|
11
|
-
mkdirSync(outputDir, { recursive: true });
|
|
12
|
-
|
|
13
|
-
console.log("Output directory ready.");
|
|
14
|
-
|
|
15
|
-
const languages = readdirSync(localesDir);
|
|
16
|
-
|
|
17
|
-
languages.forEach((lang) => {
|
|
18
|
-
const messagesPath = join(localesDir, lang, "messages.json");
|
|
19
|
-
|
|
20
|
-
if (!existsSync(messagesPath)) return;
|
|
21
|
-
|
|
22
|
-
const input = JSON.parse(readFileSync(messagesPath, "utf8"));
|
|
23
|
-
|
|
24
|
-
let output = "";
|
|
25
|
-
|
|
26
|
-
for (const key in input) {
|
|
27
|
-
output += `@${key}\n`;
|
|
28
|
-
output += `${input[key].message}\n\n`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const outputPath = join(outputDir, `${lang}.i18n`);
|
|
32
|
-
writeFileSync(outputPath, output);
|
|
33
|
-
|
|
34
|
-
console.log(`${lang}.i18n created`);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
console.log("All conversions completed.");
|