vite-plugin-shipi18n 1.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.
- package/README.md +312 -0
- package/package.json +35 -0
- package/src/index.js +189 -0
package/README.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# vite-plugin-shipi18n
|
|
2
|
+
|
|
3
|
+
> Automatic i18n translation at build time for Vite projects
|
|
4
|
+
|
|
5
|
+
Translate your locale files automatically during the build process using [Shipi18n](https://shipi18n.com) API. No manual copy-pasting, no external tools - just add the plugin and run `npm run build`.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ā” **Zero-Config** - Works out of the box with sensible defaults
|
|
10
|
+
- š **Build-Time Translation** - Translations happen during Vite build, not at runtime
|
|
11
|
+
- š¾ **Smart Caching** - Only translates changed content (hash-based caching)
|
|
12
|
+
- š **Multi-Language Support** - Translate to multiple languages in one build
|
|
13
|
+
- š§ **Preserves Placeholders** - Keeps `{{name}}`, `{count}`, `%s`, etc. intact
|
|
14
|
+
- š¦ **Framework Agnostic** - Works with any i18n library (react-i18next, vue-i18n, etc.)
|
|
15
|
+
- šÆ **Production Ready** - Battle-tested caching and error handling
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install vite-plugin-shipi18n --save-dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Get Your API Key
|
|
26
|
+
|
|
27
|
+
Sign up at [shipi18n.com](https://shipi18n.com) to get your free API key (takes 30 seconds, no credit card required).
|
|
28
|
+
|
|
29
|
+
Free tier includes:
|
|
30
|
+
- 60 requests/minute
|
|
31
|
+
- 10,000 characters/month
|
|
32
|
+
- Perfect for small to medium projects
|
|
33
|
+
|
|
34
|
+
### 2. Configure the Plugin
|
|
35
|
+
|
|
36
|
+
Add to your `vite.config.js`:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
import { defineConfig } from 'vite'
|
|
40
|
+
import shipi18n from 'vite-plugin-shipi18n'
|
|
41
|
+
|
|
42
|
+
export default defineConfig({
|
|
43
|
+
plugins: [
|
|
44
|
+
shipi18n({
|
|
45
|
+
apiKey: process.env.VITE_SHIPI18N_API_KEY,
|
|
46
|
+
targetLanguages: ['es', 'fr', 'de', 'ja'],
|
|
47
|
+
sourceDir: 'public/locales/en',
|
|
48
|
+
outputDir: 'public/locales'
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Create Your Source Locale File
|
|
55
|
+
|
|
56
|
+
Create `public/locales/en/translation.json`:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"welcome": "Welcome to our app!",
|
|
61
|
+
"greeting": "Hello, {{name}}!",
|
|
62
|
+
"message_count": "You have {{count}} messages"
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 4. Build Your Project
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run build
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The plugin will automatically:
|
|
73
|
+
1. Read your English locale files
|
|
74
|
+
2. Translate them to Spanish, French, German, and Japanese
|
|
75
|
+
3. Save translated files to `public/locales/es/`, `public/locales/fr/`, etc.
|
|
76
|
+
4. Cache results for faster subsequent builds
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
### Plugin Options
|
|
81
|
+
|
|
82
|
+
| Option | Type | Default | Description |
|
|
83
|
+
|--------|------|---------|-------------|
|
|
84
|
+
| `apiKey` | `string` | **required** | Your Shipi18n API key |
|
|
85
|
+
| `targetLanguages` | `string[]` | **required** | Array of target language codes (e.g., `['es', 'fr', 'de']`) |
|
|
86
|
+
| `sourceDir` | `string` | `'public/locales/en'` | Directory containing source locale files |
|
|
87
|
+
| `outputDir` | `string` | `'public/locales'` | Directory where translated files will be saved |
|
|
88
|
+
| `sourceLanguage` | `string` | `'en'` | Source language code |
|
|
89
|
+
| `apiUrl` | `string` | Shipi18n production URL | Custom API URL (for self-hosted instances) |
|
|
90
|
+
| `cache` | `boolean` | `true` | Enable smart caching |
|
|
91
|
+
| `cacheDir` | `string` | `'node_modules/.cache/vite-plugin-shipi18n'` | Cache directory path |
|
|
92
|
+
|
|
93
|
+
### Full Configuration Example
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
import { defineConfig } from 'vite'
|
|
97
|
+
import shipi18n from 'vite-plugin-shipi18n'
|
|
98
|
+
|
|
99
|
+
export default defineConfig({
|
|
100
|
+
plugins: [
|
|
101
|
+
shipi18n({
|
|
102
|
+
// Required
|
|
103
|
+
apiKey: process.env.VITE_SHIPI18N_API_KEY,
|
|
104
|
+
targetLanguages: ['es', 'fr', 'de', 'ja', 'zh', 'pt', 'ru', 'ar'],
|
|
105
|
+
|
|
106
|
+
// Optional (with defaults shown)
|
|
107
|
+
sourceDir: 'public/locales/en',
|
|
108
|
+
outputDir: 'public/locales',
|
|
109
|
+
sourceLanguage: 'en',
|
|
110
|
+
cache: true,
|
|
111
|
+
cacheDir: 'node_modules/.cache/vite-plugin-shipi18n'
|
|
112
|
+
})
|
|
113
|
+
]
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## How It Works
|
|
118
|
+
|
|
119
|
+
1. **Build Hook**: Plugin runs during Vite's build process (`buildStart` hook)
|
|
120
|
+
2. **File Discovery**: Scans `sourceDir` for all `.json` files
|
|
121
|
+
3. **Cache Check**: Checks if file content has changed (using MD5 hash)
|
|
122
|
+
4. **API Call**: If changed, calls Shipi18n API to translate
|
|
123
|
+
5. **File Generation**: Writes translated JSON files to `outputDir/<lang>/<filename>.json`
|
|
124
|
+
6. **Cache Update**: Saves translation results for future builds
|
|
125
|
+
|
|
126
|
+
### Caching Strategy
|
|
127
|
+
|
|
128
|
+
The plugin uses content-based hashing to determine if translation is needed:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
Hash = MD5(file_content + target_languages)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- ā
**Changed content** = Re-translate
|
|
135
|
+
- ā
**Added languages** = Re-translate
|
|
136
|
+
- āļø **Unchanged** = Use cache
|
|
137
|
+
|
|
138
|
+
Cache files are stored in `node_modules/.cache/vite-plugin-shipi18n/` and are safe to delete.
|
|
139
|
+
|
|
140
|
+
## Usage with i18n Libraries
|
|
141
|
+
|
|
142
|
+
### React + react-i18next
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
// src/i18n.js
|
|
146
|
+
import i18n from 'i18next'
|
|
147
|
+
import { initReactI18next } from 'react-i18next'
|
|
148
|
+
|
|
149
|
+
i18n
|
|
150
|
+
.use(initReactI18next)
|
|
151
|
+
.init({
|
|
152
|
+
fallbackLng: 'en',
|
|
153
|
+
lng: 'en',
|
|
154
|
+
resources: {} // Resources loaded from public/locales
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// Load translations dynamically
|
|
158
|
+
const loadLanguage = async (lng) => {
|
|
159
|
+
const translation = await fetch(`/locales/${lng}/translation.json`)
|
|
160
|
+
const data = await translation.json()
|
|
161
|
+
i18n.addResourceBundle(lng, 'translation', data)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default i18n
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Vue + vue-i18n
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// src/i18n.js
|
|
171
|
+
import { createI18n } from 'vue-i18n'
|
|
172
|
+
|
|
173
|
+
const i18n = createI18n({
|
|
174
|
+
locale: 'en',
|
|
175
|
+
fallbackLocale: 'en',
|
|
176
|
+
messages: {} // Messages loaded from public/locales
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
export default i18n
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Next.js + next-i18next
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
// next-i18next.config.js
|
|
186
|
+
module.exports = {
|
|
187
|
+
i18n: {
|
|
188
|
+
locales: ['en', 'es', 'fr', 'de'],
|
|
189
|
+
defaultLocale: 'en',
|
|
190
|
+
},
|
|
191
|
+
localePath: './public/locales'
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Supported Languages
|
|
196
|
+
|
|
197
|
+
The plugin supports **100+ languages** via Google Cloud Translation API, including:
|
|
198
|
+
|
|
199
|
+
- **es** - Spanish
|
|
200
|
+
- **fr** - French
|
|
201
|
+
- **de** - German
|
|
202
|
+
- **ja** - Japanese
|
|
203
|
+
- **zh** - Chinese (Simplified)
|
|
204
|
+
- **zh-TW** - Chinese (Traditional)
|
|
205
|
+
- **pt** - Portuguese
|
|
206
|
+
- **ru** - Russian
|
|
207
|
+
- **ar** - Arabic
|
|
208
|
+
- **hi** - Hindi
|
|
209
|
+
- **ko** - Korean
|
|
210
|
+
- **it** - Italian
|
|
211
|
+
- **nl** - Dutch
|
|
212
|
+
- **pl** - Polish
|
|
213
|
+
- **tr** - Turkish
|
|
214
|
+
- **vi** - Vietnamese
|
|
215
|
+
- **th** - Thai
|
|
216
|
+
- **id** - Indonesian
|
|
217
|
+
- **sv** - Swedish
|
|
218
|
+
- **and 85+ more...**
|
|
219
|
+
|
|
220
|
+
See the [shipi18n-react-example](https://github.com/shipi18n/shipi18n-react-example/blob/main/src/constants/languages.js) repository for the complete list of 100+ supported language codes.
|
|
221
|
+
|
|
222
|
+
## Example Project
|
|
223
|
+
|
|
224
|
+
See the [example/](./example) directory for a complete working example with React + react-i18next.
|
|
225
|
+
|
|
226
|
+
### Run the Example
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
cd example
|
|
230
|
+
npm install
|
|
231
|
+
cp .env.example .env
|
|
232
|
+
# Add your API key to .env
|
|
233
|
+
npm run build # Generates translations
|
|
234
|
+
npm run dev # Run the app
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## FAQ
|
|
238
|
+
|
|
239
|
+
### Does this work with any i18n library?
|
|
240
|
+
|
|
241
|
+
Yes! The plugin simply generates translated JSON files. It works with:
|
|
242
|
+
- react-i18next
|
|
243
|
+
- vue-i18n
|
|
244
|
+
- next-i18next
|
|
245
|
+
- Any library that loads locale files from a directory
|
|
246
|
+
|
|
247
|
+
### When does translation happen?
|
|
248
|
+
|
|
249
|
+
During the Vite build process (`vite build`), **not** at runtime. This means:
|
|
250
|
+
- ā
Zero runtime overhead
|
|
251
|
+
- ā
All translations are static files
|
|
252
|
+
- ā
Works offline after build
|
|
253
|
+
- ā
No API calls from users' browsers
|
|
254
|
+
|
|
255
|
+
### What if I change my English text?
|
|
256
|
+
|
|
257
|
+
The plugin detects changes and re-translates automatically. Caching ensures only changed files are re-translated.
|
|
258
|
+
|
|
259
|
+
### Can I translate from a language other than English?
|
|
260
|
+
|
|
261
|
+
Yes! Use the `sourceLanguage` option:
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
shipi18n({
|
|
265
|
+
sourceLanguage: 'es', // Translate from Spanish
|
|
266
|
+
targetLanguages: ['en', 'fr', 'de']
|
|
267
|
+
})
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### What about placeholders?
|
|
271
|
+
|
|
272
|
+
Placeholders are automatically preserved:
|
|
273
|
+
- `{{name}}` ā stays as `{{name}}`
|
|
274
|
+
- `{count}` ā stays as `{count}}`
|
|
275
|
+
- `%s` ā stays as `%s`
|
|
276
|
+
- `<0>` ā stays as `<0>`
|
|
277
|
+
|
|
278
|
+
### Can I use this in CI/CD?
|
|
279
|
+
|
|
280
|
+
Yes! Set your API key as an environment variable in your CI/CD pipeline:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# GitHub Actions
|
|
284
|
+
VITE_SHIPI18N_API_KEY=${{ secrets.SHIPI18N_API_KEY }}
|
|
285
|
+
|
|
286
|
+
# GitLab CI
|
|
287
|
+
VITE_SHIPI18N_API_KEY: $SHIPI18N_API_KEY
|
|
288
|
+
|
|
289
|
+
# Environment variable
|
|
290
|
+
export VITE_SHIPI18N_API_KEY=sk_live_...
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Does it slow down my build?
|
|
294
|
+
|
|
295
|
+
First build: Yes, translations take time (depends on file size and number of languages).
|
|
296
|
+
Subsequent builds: No, cached translations are used instantly.
|
|
297
|
+
|
|
298
|
+
**Tip**: Cache `node_modules/.cache/vite-plugin-shipi18n/` in your CI/CD for faster builds.
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
MIT
|
|
303
|
+
|
|
304
|
+
## Support
|
|
305
|
+
|
|
306
|
+
- [Documentation](https://shipi18n.com/docs)
|
|
307
|
+
- [GitHub Issues](https://github.com/Shipi18n/vite-plugin-shipi18n/issues)
|
|
308
|
+
- [Discord Community](https://discord.gg/shipi18n)
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
Built with [Shipi18n](https://shipi18n.com) - Smart translation API for developers
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-shipi18n",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Vite plugin for automatic i18n translation at build time using Shipi18n API",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"src/index.js",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
|
|
13
|
+
"test:watch": "NODE_OPTIONS='--experimental-vm-modules' jest --watch"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"vite",
|
|
17
|
+
"vite-plugin",
|
|
18
|
+
"i18n",
|
|
19
|
+
"translation",
|
|
20
|
+
"localization",
|
|
21
|
+
"shipi18n"
|
|
22
|
+
],
|
|
23
|
+
"author": "Shipi18n",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/Shipi18n/vite-plugin-shipi18n.git"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"vite": "^4.0.0 || ^5.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"jest": "^29.7.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import crypto from 'crypto'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Vite plugin for automatic i18n translation using Shipi18n API
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} options - Plugin configuration
|
|
9
|
+
* @param {string} options.apiKey - Shipi18n API key (required)
|
|
10
|
+
* @param {string} options.apiUrl - Shipi18n API URL (optional, defaults to production)
|
|
11
|
+
* @param {string[]} options.targetLanguages - Languages to translate to (required)
|
|
12
|
+
* @param {string} options.sourceDir - Directory containing source locale files (default: 'public/locales/en')
|
|
13
|
+
* @param {string} options.outputDir - Directory to write translated files (default: 'public/locales')
|
|
14
|
+
* @param {string} options.sourceLanguage - Source language code (default: 'en')
|
|
15
|
+
* @param {boolean} options.cache - Enable caching (default: true)
|
|
16
|
+
* @param {string} options.cacheDir - Cache directory (default: 'node_modules/.cache/vite-plugin-shipi18n')
|
|
17
|
+
*/
|
|
18
|
+
export default function shipi18nPlugin(options = {}) {
|
|
19
|
+
const {
|
|
20
|
+
apiKey,
|
|
21
|
+
apiUrl = 'https://x9527l3blg.execute-api.us-east-1.amazonaws.com',
|
|
22
|
+
targetLanguages = [],
|
|
23
|
+
sourceDir = 'public/locales/en',
|
|
24
|
+
outputDir = 'public/locales',
|
|
25
|
+
sourceLanguage = 'en',
|
|
26
|
+
cache = true,
|
|
27
|
+
cacheDir = 'node_modules/.cache/vite-plugin-shipi18n'
|
|
28
|
+
} = options
|
|
29
|
+
|
|
30
|
+
// Validation
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
throw new Error('vite-plugin-shipi18n: apiKey is required')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!targetLanguages || targetLanguages.length === 0) {
|
|
36
|
+
throw new Error('vite-plugin-shipi18n: targetLanguages is required')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let config
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
name: 'vite-plugin-shipi18n',
|
|
43
|
+
|
|
44
|
+
configResolved(resolvedConfig) {
|
|
45
|
+
config = resolvedConfig
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async buildStart() {
|
|
49
|
+
const root = config.root || process.cwd()
|
|
50
|
+
const sourcePath = path.resolve(root, sourceDir)
|
|
51
|
+
const outputPath = path.resolve(root, outputDir)
|
|
52
|
+
const cachePath = path.resolve(root, cacheDir)
|
|
53
|
+
|
|
54
|
+
console.log('\nš Shipi18n: Starting translation process...')
|
|
55
|
+
console.log(` Source: ${sourceDir}`)
|
|
56
|
+
console.log(` Target languages: ${targetLanguages.join(', ')}`)
|
|
57
|
+
|
|
58
|
+
// Check if source directory exists
|
|
59
|
+
if (!fs.existsSync(sourcePath)) {
|
|
60
|
+
console.warn(`ā ļø Warning: Source directory not found: ${sourcePath}`)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Create cache directory if needed
|
|
65
|
+
if (cache && !fs.existsSync(cachePath)) {
|
|
66
|
+
fs.mkdirSync(cachePath, { recursive: true })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Find all JSON files in source directory
|
|
70
|
+
const sourceFiles = fs.readdirSync(sourcePath)
|
|
71
|
+
.filter(file => file.endsWith('.json'))
|
|
72
|
+
|
|
73
|
+
if (sourceFiles.length === 0) {
|
|
74
|
+
console.warn(`ā ļø Warning: No JSON files found in ${sourcePath}`)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(` Found ${sourceFiles.length} source file(s)`)
|
|
79
|
+
|
|
80
|
+
// Process each file
|
|
81
|
+
for (const fileName of sourceFiles) {
|
|
82
|
+
const sourceFilePath = path.join(sourcePath, fileName)
|
|
83
|
+
const sourceContent = fs.readFileSync(sourceFilePath, 'utf-8')
|
|
84
|
+
|
|
85
|
+
let sourceJson
|
|
86
|
+
try {
|
|
87
|
+
sourceJson = JSON.parse(sourceContent)
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`ā Error parsing ${fileName}: ${error.message}`)
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Calculate hash for caching
|
|
94
|
+
const contentHash = crypto
|
|
95
|
+
.createHash('md5')
|
|
96
|
+
.update(sourceContent + targetLanguages.join(','))
|
|
97
|
+
.digest('hex')
|
|
98
|
+
|
|
99
|
+
const cacheFile = path.join(cachePath, `${fileName}.${contentHash}.json`)
|
|
100
|
+
|
|
101
|
+
// Check cache
|
|
102
|
+
let translations = null
|
|
103
|
+
if (cache && fs.existsSync(cacheFile)) {
|
|
104
|
+
console.log(` ā ${fileName}: Using cached translations`)
|
|
105
|
+
try {
|
|
106
|
+
translations = JSON.parse(fs.readFileSync(cacheFile, 'utf-8'))
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.warn(` ā ļø Cache corrupted for ${fileName}, re-translating...`)
|
|
109
|
+
translations = null
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Translate if not cached
|
|
114
|
+
if (!translations) {
|
|
115
|
+
console.log(` ā³ ${fileName}: Translating to ${targetLanguages.length} language(s)...`)
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
translations = await translateJSON({
|
|
119
|
+
apiKey,
|
|
120
|
+
apiUrl,
|
|
121
|
+
json: sourceJson,
|
|
122
|
+
sourceLanguage,
|
|
123
|
+
targetLanguages
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// Save to cache
|
|
127
|
+
if (cache) {
|
|
128
|
+
fs.writeFileSync(cacheFile, JSON.stringify(translations, null, 2))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(` ā ${fileName}: Translation complete`)
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error(` ā ${fileName}: Translation failed - ${error.message}`)
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Write translated files
|
|
139
|
+
for (const langCode of targetLanguages) {
|
|
140
|
+
if (!translations[langCode]) {
|
|
141
|
+
console.warn(` ā ļø ${fileName}: No translation for ${langCode}`)
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const outputLangDir = path.join(outputPath, langCode)
|
|
146
|
+
if (!fs.existsSync(outputLangDir)) {
|
|
147
|
+
fs.mkdirSync(outputLangDir, { recursive: true })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const outputFile = path.join(outputLangDir, fileName)
|
|
151
|
+
fs.writeFileSync(
|
|
152
|
+
outputFile,
|
|
153
|
+
JSON.stringify(translations[langCode], null, 2)
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('ā
Shipi18n: Translation complete!\n')
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Call Shipi18n API to translate JSON
|
|
165
|
+
*/
|
|
166
|
+
async function translateJSON({ apiKey, apiUrl, json, sourceLanguage, targetLanguages }) {
|
|
167
|
+
const response = await fetch(`${apiUrl}/api/translate`, {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: {
|
|
170
|
+
'Content-Type': 'application/json',
|
|
171
|
+
'X-API-Key': apiKey
|
|
172
|
+
},
|
|
173
|
+
body: JSON.stringify({
|
|
174
|
+
inputMethod: 'json',
|
|
175
|
+
jsonInput: JSON.stringify(json),
|
|
176
|
+
sourceLanguage,
|
|
177
|
+
targetLanguages: JSON.stringify(targetLanguages),
|
|
178
|
+
preservePlaceholders: 'true'
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
const error = await response.json().catch(() => ({}))
|
|
184
|
+
throw new Error(error.message || `API request failed: ${response.status}`)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const data = await response.json()
|
|
188
|
+
return data.translations || {}
|
|
189
|
+
}
|