@ayinza_tech/i18n-config
v1.2.6
Published
Shared i18next configuration for multiple portals
Readme
ayinza-i18n-config
Shared i18next configuration and formatting utilities for Ayinza portals.
This package centralizes i18n initialization, sensible defaults, and a set of formatters (currency, number, percent, date/time, relative time) that are integrated with i18next's interpolation system. It also provides React hooks for easy consumption in React apps.
Table of contents
- About
- Installation
- Quick start
- API
- Configuration
- Examples
- Translation extraction
- Testing
- Development
- License
About
The library bundles a default i18n configuration (detection, backend, namespaces, and formatters) and exposes helpers to initialize i18next, access the global i18n instance, and consume localization + formatting helpers in React components.
It is intentionally lightweight and designed to be used as a shared dependency across multiple portals that want consistent localization behavior.
Installation
Install the package and peer dependencies (peer dependencies are required by consumers and not bundled):
# Using npm
npm install @ayinza_tech/i18n-config i18next react react-i18next
# Using yarn
yarn add @ayinza_tech/i18n-config i18next react react-i18nextAlso install the optional runtime backends used by this package (the package declares them as dependencies):
npm install i18next-http-backend i18next-browser-languagedetectorNote: This package declares i18next, react, and react-i18next as
peerDependencies — install versions compatible with your app.
Quick start
Initialize i18n at application startup (for example in src/main.tsx or
src/index.tsx in a React app):
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import { initializeI18n } from "@ayinza_tech/i18n-config";
// Optional: pass overrides to customize backend, supported languages, or
// formatters.
initializeI18n({
config: {
portalName: "My Portal",
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json",
customHeaders: { "X-Portal": "my-portal" },
},
supportedLngs: ["en", "ar", "fr"],
},
})
.then(() => {
const root = createRoot(document.getElementById("root")!);
root.render(<App />);
})
.catch((err) => console.error(err));The function returns the initialized i18next instance and also registers a set of custom formatters so you may use them inside translations (interpolation formatters) or via the provided hooks.
SSR note:
initializeI18ntouches browser-only globals to set thedirattribute. The implementation now guards those calls, but you should still run initialization on the client (e.g., inside auseEffector Next.jsuse cliententry point) to ensure detectors and DOM updates work.
API
Top-level exports (from src/index.ts):
initializeI18n(options?: I18nInitOptions): Promise<i18n>— initialize the i18next instance with defaults merged with your overrides.getI18nInstance(): i18n— access the i18next singleton.getFormatters(): I18nFormatters— get the formatter instance (throws if not initialized).defaultConfig— the default configuration object used byinitializeI18n.createI18nConfig(partial?: Partial<I18nConfig>)— returns a fully merged config object without initializing i18next; useful for building configs in build-time tooling or sharing defaults across portals.createTranslationSnapshot(options: CreateTranslationSnapshotOptions)— flatten translation JSON trees into a comparable snapshot for diffing.collectNewTranslationKeys(options: CollectNewTranslationKeysOptions)— compute keys that were added between two snapshots.handleNewTranslationKeys(options: HandleNewTranslationKeysOptions)— push detected keys to a remote endpoint or log them during dry runs.useFormatting()— React hook providing formatting helpers bound to the current language.useI18n()— combined hook returning bothuseTranslation()props and formatting helpers.- Both hooks expect
initializeI18nto have completed; call initialization in your app bootstrap before rendering components that use them, otherwisegetFormatters()will throw. - Re-exports:
useTranslation,Trans,Translationfromreact-i18next.
Types exported (from src/types.ts):
I18nConfig— top-level configuration object shape.I18nInitOptions— options forinitializeI18n.FormattersConfig,LocaleMapping— configuration shapes for formatters.
Formatters class: I18nFormatters provides methods such as:
formatCurrency(amount, language, currency?, options?)formatNumber(value, language, options?)formatPercent(value, language, options?)formatDate(date, language, options?)formatTime(date, language, options?)formatDateTime(date, language, options?)formatRelativeTime(value, unit, language, options?)
These are already wired into i18next as interpolation formatters named
currency, number, percent, date, time, datetime, and relative.
Every formatter catches Intl errors and falls back to simple strings (for
example, returning INVALID 100 for a bad currency code or toLocaleString()
for an invalid date). This keeps your UI from crashing, but you may still see
console warnings when supplying malformed input.
Configuration
defaultConfig (summary):
- backend: { loadPath }
- detection: browser language detection configuration
- fallbackLng:
en - supportedLngs:
["en","ar","fr","es"](override to match your portal to avoid loading unused bundles) - defaultNS / ns: namespaces used (feel free to switch to
commonif that is your primary namespace) - interpolation.escapeValue: false
- formatters: default formatter configuration (defaultCurrency
USD,fallbackLocale: "en-US"when no locale mapping matches) - react:
{ useSuspense: true }but you can extend it withbindI18n/bindI18nStoreto match your React rendering mode
You can override only the pieces you need — initializeI18n merges defaults
with your partial config. If you need a pure helper (no side effects) to
assemble configs, use createI18nConfig({ ...overrides }) and feed the result
into your own bootstrap logic.
Common overrides:
supportedLngs: keep this list scoped to the locales your portal actually serves so language detection stays predictable and bundles stay small.ns/defaultNS: if you share acommonnamespace across portals, consider settingdefaultNS: "common"and trimming thensarray.react: setuseSuspense: falsefor legacy React renderers or providebindI18n: "languageChanged"when coordinating with data-fetching layers.formatters.fallbackLocale: change this if your organization defaults to a locale other than English; it is used whenever a language code is missing from the locale mapping tables.- Module format: The published package currently ships as an ES module build
(per
tsconfig.json). If your tooling expects CommonJS, configure your bundler to transpile ESM or consider contributing a dual-build setup.
Examples
Use translation + formatting together in a React component:
import React from "react";
import { useI18n } from "@ayinza_tech/i18n-config";
function Price({ amount }: { amount: number }) {
const { t, formatCurrency } = useI18n();
return (
<div>
<h3>{t("priceHeading")}</h3>
<p>{formatCurrency(amount)}</p>
</div>
);
}Using formatters directly (non-React):
import { initializeI18n, getFormatters } from "@ayinza_tech/i18n-config";
async function start() {
await initializeI18n();
const fmt = getFormatters();
console.log(fmt.formatCurrency(19.99, "en", "USD"));
}Using interpolation in translation strings (example en/common.json):
{
"price": "{{value, currency}}"
}Then t('price', { value: 19.99, currency: 'EUR' }) will use the registered
currency formatter.
Integrating Across Multiple Portals
When sharing this package across portals, keep initialization centralized so each shell bootstraps consistently:
- Create a thin wrapper (e.g.,
packages/i18n/client.ts) that callsinitializeI18nwith portal-specific overrides such as namespace lists or branding headers. - Import only that wrapper from each portal entry point to keep behavior aligned and avoid forgetting required detectors/backends.
- Re-export helpers (
useI18n,getFormatters) from your shell layer so downstream micro frontends consume the same singleton instance. - For SSR/Next.js, run
initializeI18ninside client components or auseEffectguard to allow detectors to access browser APIs, then hydrate shared hooks.
Example shared bootstrap that portals can reuse:
// packages/i18n/bootstrap.ts
import {
initializeI18n,
getFormatters,
useI18n,
} from "@ayinza_tech/i18n-config";
export async function setupPortalI18n(portalName: string) {
await initializeI18n({
config: {
portalName,
backend: {
loadPath: `/locales/${portalName}/{{lng}}/{{ns}}.json`,
},
supportedLngs: ["en", "fr", "sw"],
},
});
return {
i18n: getFormatters(),
useI18n,
};
}
// portal-a/src/main.tsx
import { setupPortalI18n } from "@ayinza/portal-shared/i18n";
setupPortalI18n("portal-a").then(() => {
// mount React app here, all child components can call useI18n()
});This pattern keeps each portal lightweight while ensuring updates to the core localization stack propagate everywhere by upgrading just this package.
Translation extraction
The package now ships with light wrappers around
i18next-parser so every portal
can reuse the same extraction defaults and push workflow:
Config helper. Create
i18next-parser.config.mjs(or.cjs) that simply exportscreateI18nextParserConfig({ /* overrides */ }). The helper sets consistent defaults (lexers, separators, indentation,createOldCatalogs, etc.) so every portal parses sources the same way.// i18next-parser.config.mjs import { createI18nextParserConfig } from "@ayinza_tech/i18n-config"; export default createI18nextParserConfig({ input: ["src/**/*.{ts,tsx}"], locales: ["en"], output: "locales/$LOCALE/$NAMESPACE.json", });Detect new keys. Capture a snapshot before and after running the parser (usually for the default locale) by loading your locale JSON and passing it to
createTranslationSnapshot, then callcollectNewTranslationKeysto compute the delta.import { readFile } from "node:fs/promises"; import path from "node:path"; import { createTranslationSnapshot, collectNewTranslationKeys, handleNewTranslationKeys, } from "@ayinza_tech/i18n-config"; const localesRoot = path.resolve("locales"); const namespaces = ["translation", "common"]; async function loadNamespaces(locale: string) { const entries = await Promise.all( namespaces.map(async (namespace) => { const filePath = path.join(localesRoot, locale, `${namespace}.json`); const raw = await readFile(filePath, "utf8"); return [namespace, JSON.parse(raw) as Record<string, unknown>]; }) ); return Object.fromEntries(entries) as Record< string, Record<string, unknown> >; } const before = createTranslationSnapshot({ locale: "en", namespaces: await loadNamespaces("en"), }); // Run `npx i18next --config i18next-parser.config.mjs "src/**/*.{ts,tsx}"` const after = createTranslationSnapshot({ locale: "en", namespaces: await loadNamespaces("en"), }); const newKeys = collectNewTranslationKeys({ previous: before, next: after });Push or log. Pass the detected keys to
handleNewTranslationKeysto run a dry-run or POST them to your translation management service. Configure the helper with portal-specific metadata so CI logs stay readable.await handleNewTranslationKeys({ newKeys, pushConfig: { portalName: "admin-shell", pushUrl: process.env.TRANSLATION_PUSH_URL, authorizationToken: process.env.TRANSLATION_PUSH_TOKEN, dryRun: process.env.CI === "true" && process.env.DRY_RUN === "true", }, });
If pushUrl is omitted or dryRun is true, the helper only logs detected
keys. Provide a custom fetchImpl via ParserPushConfig when running on Node
versions older than 18 (which lack global.fetch).
Testing
There are unit tests for I18nFormatters (see src/formatters.test.ts) and
for the config merge helper (see src/config.test.ts). Run tests with the
provided npm scripts:
npm test
# watch mode during development
npm run test:watchNote: this repository includes Jest devDependencies configured for TypeScript.
Development
- Build:
npm run build(compiles todist/usingtsc) - Watch:
npm run build:watch - Test:
npm testornpm run test:watch
If you intend to contribute, please run tests and add coverage for new features.
License
MIT — see the LICENSE file in this repository.
Next steps & suggestions
- Add CI (GitHub Actions) to run tests and build on push/PR.
- Add usage examples / Storybook snippets for React components that depend on formatting.
- Consider publishing with changelog and semantic-release for automated releases.
- Allow consumers to provide a custom logger/debug handler so initialization logs can be routed through their monitoring stack instead of console.
