translate-projects-core
v0.0.11
Published
Easily globalize your projects
Readme
Translate projects core
Easily globalize your projects
Core library of Translate Projects. It sends your source texts to the Translate Projects API, waits for the translation with a progress bar in the terminal, and returns the translated texts. A local cache skips files that haven't changed, so a second run translates nothing.
Using React, Next.js, Astro or Docusaurus? Install that integration instead (
translate-projects-react,translate-projects-nextjs,translate-projects-astro,translate-projects-docusaurus). It has a ready-made command and writes the translated files for you. Use this package directly only for plain JSON files or to build your own integration.
Requirements
- Node.js 20 or newer (uses the global
fetchandcrypto). - A Translate Projects project and its API key, from translateprojects.dev.
sourceLangmust be the default language of your project on Translate Projects, otherwise the API rejects the texts.
Installation
npm install translate-projects-coreAPI key
Put the project's API key in the TRANSLATE_PROJECTS_API_KEY environment variable. Don't commit it.
# .env (add .env to .gitignore)
TRANSLATE_PROJECTS_API_KEY=your-project-api-keyThe library never reads the variable on its own: you pass it as apiKey (see below). Without a key, no request is made and the log shows MISSING_API_KEY.
Usage: translate a folder of JSON files
Source files live in locales/<sourceLang>/, one JSON per file. Values must be strings (nested objects are not supported by this script; flatten them first).
locales/
es/
common.json ← { "greeting": "Buenos días", "thanks": "Gracias por tu ayuda" }Save this as translate.js next to locales/:
const fs = require('fs');
const path = require('path');
const { validateChangesFiles } = require('translate-projects-core');
const {
assignHashKeysJson,
makeTranslations,
processDirectoryCache,
readJsonFile,
syncResources,
updateFileCache,
} = require('translate-projects-core/utils');
const apiKey = process.env.TRANSLATE_PROJECTS_API_KEY;
const sourceLang = 'es'; // your project's default language
const targetLangs = ['en', 'pt-BR', 'fr'];
(async () => {
// 1. Hash every source file and ask the API which ones changed
const { filesCache, filesPath } = await processDirectoryCache({
dir: path.resolve('locales', sourceLang),
});
const changed = (await validateChangesFiles({ apiKey, data: filesCache }))
?.data;
if (!changed) return (process.exitCode = 1);
// 2. Send the texts of new or changed files
for (const [hash, file] of Object.entries(filesPath)) {
if (!changed[hash] && Object.keys(file.sources).length) continue;
const sources = await assignHashKeysJson(readJsonFile(file.path));
const synced = await syncResources({
apiKey,
sourceLang,
typeProject: 'json',
data: sources,
route_file: file.path,
cache_hash: file.cache_hash,
});
if (synced === null) return (process.exitCode = 1);
await updateFileCache({ fileHash: hash, sources });
file.sources = sources;
}
// 3. Translate what is missing and write locales/<lang>/<file>.json
for (const [hash, file] of Object.entries(filesPath)) {
for (const lang of targetLangs) {
let translations = file.translations[lang] ?? {};
const missing = Object.keys(file.sources).some(
(key) => !(key in translations)
);
if (missing) {
const all = await makeTranslations({
apiKey,
sourceLang,
targetLang: lang,
route_file: file.path,
cache_hash: file.cache_hash,
});
if (all === null) {
process.exitCode = 1; // keep the previous file, retry next run
continue;
}
// the API returns every text of the project; keep this file's keys
translations = Object.fromEntries(
Object.keys(file.sources)
.filter((key) => key in all)
.map((key) => [key, all[key]])
);
await updateFileCache({
fileHash: hash,
translations: { [lang]: translations },
});
}
const out = path.resolve('locales', lang, path.basename(file.path));
fs.mkdirSync(path.dirname(out), { recursive: true });
fs.writeFileSync(out, JSON.stringify(translations, null, 2));
}
}
})();Run it from the folder that contains locales/:
set -a; . ./.env; set +a # or export TRANSLATE_PROJECTS_API_KEY=...
node translate.jsThe exit code is 1 if anything failed, so you can use it in CI.
Files it generates
| File | Created by | Content |
| ---------------------------------------------------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| locales/<lang>/<file>.json | the script above | Translated texts, same keys as the source file. Only written when that language succeeded; on failure the previous file is kept. |
| ~/.config/translate-projects-core/cache.json (Windows: %USERPROFILE%\AppData\Roaming\translate-projects-core\cache.json) | this library | Hashes, sources and translations already received. |
The library itself only writes the cache file. Writing the translated files is up to the caller (the integrations do it for you).
Keys
assignHashKeysJson keeps your keys as they are, except when a key is identical to its text ({ "Hello": "Hello" }): those get a short hash as key so long texts can be used as keys.
Language codes
Use the exact codes of the API, for example en, es, fr, pt-BR, pt-PT, zh-Hans, zh-Hant. There is no plain pt or zh. The full list is exported as langs from translate-projects-core/utils and typed as TypeListLang.
Cache
- The cache is stored per project, keyed by the folder you run the command from (
process.cwd()). Always run it from the same folder. - On each run, a file is sent again only if its content changed, and a language is translated only if some key has no translation yet. A second run with no changes makes a single request and translates nothing.
- Force a full run: delete
cache.json(path above). The next run sends all files again and requests every language. To redo a single language, delete it from your own output and from the cache, or simply deletecache.json. - A corrupt
cache.jsonis treated as empty: the next run is a full run, never a crash.
Common errors
Every function logs the error and returns null instead of throwing.
| Log message | Cause | What to do |
| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| MISSING_API_KEY: set your Translate Projects API key ... | apiKey is empty: the variable is not set in this shell. | Export TRANSLATE_PROJECTS_API_KEY (or load your .env) and run again. No request was made. |
| API Error (401): UNAUTHORIZED: Token no válido | The API key is wrong, has a wrong format, or doesn't belong to any active project. | Copy the key again from your project on translateprojects.dev. Nothing was sent and existing files are kept. |
| API Error (400): ... about the language | sourceLang is not the project's default language, or a target code doesn't exist or is disabled. | Use the project's default language as sourceLang and exact codes (pt-BR, not pt). |
| Warning: ... telling that the project has no AI key | The project has no AI model or key configured. The API still answers and delivers the translations that already exist; the rest stay missing. | Configure the AI key in your project. The next run requests only the missing texts again. |
| Some keys missing in an output file | Partial translation (for example, the case above). | Nothing to clean up: the next run detects the missing keys and requests that language again. |
| Translations did not complete / Syncing sources did not complete | The progress connection closed, or got no message for 5 minutes. | Run again; finished work is kept in the cache. |
| Request failed: ... (attempt 3/3) / Max retries reached | Network error or the API is unavailable. Each request waits up to 60 s and is retried 3 times (also on 429, 502, 503, 504, 524). | Check your connection and run again. |
Configuration
| Variable | Default | Description |
| ---------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------- |
| TRANSLATE_PROJECTS_API_KEY | — | Your project's API key. Read by your script and passed as apiKey. |
| TRANSLATE_PROJECTS_API_URL | https://api.translateprojects.dev | API base URL (only to point to another backend). The progress WebSocket uses the same host. |
API reference
Main functions (translate-projects-core, also exported from translate-projects-core/utils):
| Function | Returns |
| ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| validateChangesFiles({ apiKey, data }) | data: the entries of { fileHash: contentHash } that are new or changed. |
| syncResources({ apiKey, sourceLang, data, typeProject, route_file, cache_hash? }) | Sends a flat { key: text } object and waits for it to be processed. undefined on success, null on failure. typeProject: 'json' \| 'react' \| 'next.js' \| 'astro' \| 'docusaurus'. |
| makeTranslations({ apiKey, sourceLang, targetLang, route_file, cache_hash? }) | Translates and returns the flat { key: translation } object of all the project's texts in targetLang, or null on failure. |
| getTranslations({ apiKey, targetLang }) | The existing translations in targetLang, without requesting new ones. |
| generateHashText(text) | Short (15 chars) base36 SHA-256 hash. |
Utilities (translate-projects-core/utils):
processDirectoryCache({ dir, onlyRoot?, allowedExtensions? }): scansdir(recursively unlessonlyRoot) for.jsonfiles. ReturnsfilesCache({ fileHash: contentHash }) andfilesPath({ fileHash: { path, cache_hash, sources, translations } }with what the cache already has).updateFileCache({ fileHash, sources?, translations?, version? }): merges into the cache.assignHashKeysJson(json),readJsonFile(path),langs,Logger,progressBar(taskId), plus low-level cache helpers (processFileHashCache,getProjectInfo,getProjectHashFromPath,loadCache,saveCache).
Types (translate-projects-core/types): TypeListLang, TypeProject, TypeSimpleJson, JsonBase, FilePathData, Cache, FileCache.
Development
npm install
npm run build # compiles src/ to dist/
npm test # builds, then runs the node:test suites in test/Support
If you have any questions, need help, or want to contribute, feel free to reach out:
- Website: neiderruiz.com
- GitHub: @neiderruiz
- Twitter: @neiderruiz
- YouTube: Neider Ruiz
Support the Project
If you find this project useful, consider buying me a coffee to help me keep improving it. Your support means a lot! ☕
