i18n Manager — Integration Guide

Integration Guide

How to drop the i18n Manager next to an existing project and use it to manage that project's translations — without changing your app's stack.

Integration models

The i18n Manager is a self-contained admin tool with its own database. The recommended approach is to run it as a standalone service and have your project read its translations. Your project never imports the PHP code — it only consumes data.

ModelHowBest for
A. Standalone service (recommended) Deploy the manager at its own host/vhost (e.g. https://i18n.yourcompany.com) against a dedicated database. Your project reads from its API. Any stack. Keeps concerns separate; no path conflicts.
B. Build-time data source Run the manager locally/internally, export per-language JSON, and commit those files into your project's existing i18n pipeline. Static sites, bundlers, offline runtime, no extra network dependency in production.
Why not mount it inside your project folder? The app assumes it is served at the domain root (absolute /api/, /includes/, $_SERVER['DOCUMENT_ROOT']). Running it as its own app avoids reworking those paths — and is the path these recipes assume.

The consumption endpoint

Everything your project needs comes from one endpoint:

GET {MANAGER}/api/translations_i18n.php

Query params (all optional):
  namespace=nav      # only keys in this namespace
  search=home        # ILIKE match on key/description
  format=json        # json (default) or js

Response (format=json) — a flat map of every active language:
{
  "nav.home":      { "en": "Home", "de": "Startseite" },
  "home.welcome":  { "en": "Welcome", "de": "Willkommen" }
}

With format=js the same data is returned as var i18n = {…}; (content-type application/javascript) — handy to load via a plain <script> tag. For a single-language file (e.g. to commit per locale) use /api/translations_export.php?language=de&format=json.

Look up a string as map[key][lang], falling back to a default language (usually en) when a key/language is missing.

Recipe — PHP project

Fetch the map once, cache it to a local file, and expose a tiny t() helper. This keeps production fast and resilient if the manager is briefly unreachable.

<?php
// i18n.php — drop into your project
const I18N_MANAGER  = 'https://i18n.yourcompany.com';
const I18N_CACHE    = __DIR__ . '/cache/i18n.json';
const I18N_TTL      = 300;            // seconds
const I18N_FALLBACK = 'en';

function i18n_map(): array {
    // Serve from cache if fresh
    if (is_file(I18N_CACHE) && time() - filemtime(I18N_CACHE) < I18N_TTL) {
        return json_decode(file_get_contents(I18N_CACHE), true) ?: [];
    }
    // Refresh from the manager
    $url  = I18N_MANAGER . '/api/translations_i18n.php?format=json';
    $json = @file_get_contents($url);
    if ($json !== false) {
        @mkdir(dirname(I18N_CACHE), 0775, true);
        file_put_contents(I18N_CACHE, $json, LOCK_EX);
        return json_decode($json, true) ?: [];
    }
    // Fall back to the last good cache even if stale
    return is_file(I18N_CACHE)
        ? (json_decode(file_get_contents(I18N_CACHE), true) ?: [])
        : [];
}

function t(string $key, ?string $lang = null): string {
    static $map = null;
    $map  ??= i18n_map();
    $lang ??= $_SESSION['lang'] ?? I18N_FALLBACK;
    return $map[$key][$lang]
        ?? $map[$key][I18N_FALLBACK]
        ?? $key;                       // show the key if nothing found
}
?>

<!-- usage in a template -->
<h1><?= htmlspecialchars(t('home.welcome')) ?></h1>
HTML keys: values flagged contains HTML in the manager contain markup. Print those with echo t(...) (no escaping); escape everything else with htmlspecialchars() to avoid XSS.

Recipe — JavaScript / SPA

The dashboard's own localization engine is fully reusable. Save this as i18n.js, serve it from your project, and tag elements with data-i18n just like the manager does.

// i18n.js — framework-free runtime localizer
(function (global) {
  const MANAGER  = 'https://i18n.yourcompany.com';
  const FALLBACK = 'en';
  let map = {};

  async function load() {
    const res = await fetch(`${MANAGER}/api/translations_i18n.php?format=json`);
    map = await res.json();
    return map;
  }

  function t(key, lang) {
    const e = map[key];
    return (e && (e[lang] ?? e[FALLBACK])) ?? key;
  }

  function apply(lang) {
    localStorage.setItem('lang', lang);
    document.documentElement.setAttribute('lang', lang);
    document.querySelectorAll('[data-i18n]').forEach(el => {
      const v = t(el.getAttribute('data-i18n'), lang);
      if (v != null) el.textContent = v;
    });
    document.querySelectorAll('[data-i18n-html]').forEach(el => {
      const v = t(el.getAttribute('data-i18n-html'), lang);
      if (v != null) el.innerHTML = v;            // only for trusted HTML keys
    });
    document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
      const v = t(el.getAttribute('data-i18n-placeholder'), lang);
      if (v != null) el.setAttribute('placeholder', v);
    });
  }

  global.I18n = { load, t, apply };
})(window);

// bootstrap
I18n.load().then(() => I18n.apply(localStorage.getItem('lang') || 'en'));
<!-- usage -->
<h1 data-i18n="home.welcome">Welcome</h1>
<input data-i18n-placeholder="filter.searchplaceholder" placeholder="Search…">
<script src="/i18n.js"></script>
CORS: when your project and the manager are on different origins, the browser fetch needs the manager to send Access-Control-Allow-Origin. If you can't enable that, use the build-time export instead and ship a same-origin JSON file.

Recipe — build-time export

For static sites or bundlers, snapshot the translations at build time and ship them with your app — no runtime dependency on the manager.

# Whole map (all languages) → commit as one file
curl -s "https://i18n.yourcompany.com/api/translations_i18n.php?format=json" \
     -o src/locales/i18n.json

# Or one file per language
for L in en de fr; do
  curl -s "https://i18n.yourcompany.com/api/translations_export.php?language=$L&format=json" \
       -o "src/locales/$L.json"
done

Wire that curl step into your prebuild/CI so every release picks up the latest translations.

Adding keys from your project

The bundled scanner finds translation keys in your project's markup and scaffolds the import for the manager. Point it at your project root:

# Discover keys in your project not yet in the manager's DB,
# and machine-translate them into every language.
node tools/scan_i18n_keys.js \
     --root /path/to/your/project \
     --endpoint https://i18n.yourcompany.com/api/translations_i18n.php \
     --translate \
     --out seeds

This writes seeds/new_keys.json (paste into the manager's Bulk Import tab) and seeds/new_keys.sql (run against the DB). Translators then refine and review the values in the UI.

Caching & cache-busting

Pre-production checklist

Limitations to design around

Quick reference

NeedUse
All translations, all languages (runtime)GET /api/translations_i18n.php?format=json
Same, as a JS variableGET /api/translations_i18n.php?format=js
One language, downloadable fileGET /api/translations_export.php?language=de&format=json
One namespace only…translations_i18n.php?namespace=nav
Discover new keys in your projectnode tools/scan_i18n_keys.js --root … --endpoint …
Full API referenceSwagger · ReDoc