npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

translate-projects-core

v0.0.11

Published

Easily globalize your projects

Readme

Translate projects core

Easily globalize your projects

Documentation

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 fetch and crypto).
  • A Translate Projects project and its API key, from translateprojects.dev.
  • sourceLang must be the default language of your project on Translate Projects, otherwise the API rejects the texts.

Installation

npm install translate-projects-core

API 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-key

The 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.js

The 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 delete cache.json.
  • A corrupt cache.json is 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? }): scans dir (recursively unless onlyRoot) for .json files. Returns filesCache ({ fileHash: contentHash }) and filesPath ({ 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:

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! ☕

Buy me a coffee