{"_id":"@ayinza_tech/i18n-config","_rev":"8-3fef2a4d0db34b57dc281d19657e1982","name":"@ayinza_tech/i18n-config","dist-tags":{"latest":"1.2.6"},"versions":{"1.0.0":{"name":"@ayinza_tech/i18n-config","version":"1.0.0","keywords":[],"author":{"name":"ayinza"},"license":"MIT","_id":"@ayinza_tech/i18n-config@1.0.0","maintainers":[{"name":"ayinza_tech","email":"arthur@ayinza.com"}],"homepage":"https://github.com/sseris/ayinza-i18n-config#readme","bugs":{"url":"https://github.com/sseris/ayinza-i18n-config/issues"},"dist":{"shasum":"22d75c7c0cbfa49c66515c0b6cf7fd70039e0cf7","tarball":"https://registry.npmjs.org/@ayinza_tech/i18n-config/-/i18n-config-1.0.0.tgz","fileCount":14,"integrity":"sha512-/yVkQz+Zddb7+VZjYhdUYJlQVK6kp2hZ+9JfLKIpgCc5UOnDpxBiNmpJNmDA/7JIrc/urCPkwm9iPu+/Vfnxfw==","signatures":[{"sig":"MEUCIQCmRYZOPwOeGu91A5sbn5OUSxNQzHnjAMrzn9d3mkGkKQIgMZFhWaKkpsbOJB4k37ODm0mcEvTd8O0QACsoXGFymz0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":37453},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"294779b99d862039a9b55a5720b09ca930cb1042","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc","build:watch":"tsc --watch"},"_npmUser":{"name":"ayinza_tech","email":"arthur@ayinza.com"},"repository":{"url":"git+https://github.com/sseris/ayinza-i18n-config.git","type":"git"},"_npmVersion":"10.8.2","description":"Shared i18next configuration for multiple portals","directories":{},"_nodeVersion":"24.11.0","dependencies":{"i18next-http-backend":"^3.0.2","i18next-browser-languagedetector":"^8.2.0"},"_hasShrinkwrap":false,"devDependencies":{"i18n":"^0.15.3","jest":"^30.2.0","parser":"^0.1.4","ts-jest":"^29.4.5","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.10.0","@types/react":"^19.2.2","@types/react-dom":"^19.2.2","jest-environment-jsdom":"^30.2.0"},"peerDependencies":{"react":"^19.2.0","i18next":"^25.6.1","react-i18next":"^16.2.4"},"_npmOperationalInternal":{"tmp":"tmp/i18n-config_1.0.0_1763036283762_0.3044205696892901","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@ayinza_tech/i18n-config","version":"1.2.0","keywords":[],"author":{"name":"ayinza"},"license":"MIT","_id":"@ayinza_tech/i18n-config@1.2.0","maintainers":[{"name":"ayinza_tech","email":"arthur@ayinza.com"}],"homepage":"https://github.com/sseris/ayinza-i18n-config#readme","bugs":{"url":"https://github.com/sseris/ayinza-i18n-config/issues"},"dist":{"shasum":"8abc67e89d7121db4ff6914d6b9cdee79faafe30","tarball":"https://registry.npmjs.org/@ayinza_tech/i18n-config/-/i18n-config-1.2.0.tgz","fileCount":14,"integrity":"sha512-4Ff3Q2ZpAKlwRRymLDD3n2wPMq2l3HQOKac4oBKJ25hcwcUJr0/quZGTrhg/g/2ruMVTBqi7CyMul8BMj/qAsg==","signatures":[{"sig":"MEQCIDhvGy94X1c/1cUhqWbX070PgD/n6zBX5scil+dM5l3qAiBkx8Ke5ODVoYZBhAVeE2Uh0WljbVkk0xsJUTGW7sBPuA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":44065},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"821b815c9b5f9cffb6205e9aec41d8842f0942a8","scripts":{"test":"jest","build":"tsc","test:watch":"jest --watch","build:watch":"tsc --watch"},"_npmUser":{"name":"ayinza_tech","email":"arthur@ayinza.com"},"repository":{"url":"git+https://github.com/sseris/ayinza-i18n-config.git","type":"git"},"_npmVersion":"10.8.2","description":"Shared i18next configuration for multiple portals","directories":{},"_nodeVersion":"24.11.0","dependencies":{"i18next-http-backend":"^3.0.2","i18next-browser-languagedetector":"^8.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","ts-jest":"^29.4.5","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.10.0","@types/react":"^19.2.2","i18next-parser":"^9.0.2","@types/react-dom":"^19.2.2","jest-environment-jsdom":"^30.2.0"},"peerDependencies":{"react":"^19.2.0","i18next":"^25.6.1","react-i18next":"^16.2.4"},"_npmOperationalInternal":{"tmp":"tmp/i18n-config_1.2.0_1763386415826_0.5348227580804976","host":"s3://npm-registry-packages-npm-production"}},"1.2.1":{"name":"@ayinza_tech/i18n-config","version":"1.2.1","keywords":[],"author":{"name":"ayinza"},"license":"MIT","_id":"@ayinza_tech/i18n-config@1.2.1","maintainers":[{"name":"ayinza_tech","email":"arthur@ayinza.com"}],"homepage":"https://github.com/sseris/ayinza-i18n-config#readme","bugs":{"url":"https://github.com/sseris/ayinza-i18n-config/issues"},"dist":{"shasum":"ac054007c1e6fe9ded6dad088d990427bb8feb9e","tarball":"https://registry.npmjs.org/@ayinza_tech/i18n-config/-/i18n-config-1.2.1.tgz","fileCount":18,"integrity":"sha512-VBm8VWEvVvtwL1fl5aCK0zLyFmcgY3/s2THkqMpBwkM0ZwmyHaXy8FYTt3H6UA4EbiARFDpyYK7ofpY29oGu1Q==","signatures":[{"sig":"MEQCIBIyydMJ6UAJeQKpuOKHzGYOlq8hL4Sj9dMNm0oYmFZ6AiBFksXxnReRs5z88WCLkUT5bGr2F+R+ahxJyimmeW58pQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":56366},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"821b815c9b5f9cffb6205e9aec41d8842f0942a8","scripts":{"test":"jest","build":"tsc","test:watch":"jest --watch","build:watch":"tsc --watch"},"_npmUser":{"name":"ayinza_tech","email":"arthur@ayinza.com"},"repository":{"url":"git+https://github.com/sseris/ayinza-i18n-config.git","type":"git"},"_npmVersion":"10.8.2","description":"Shared i18next configuration for multiple portals","directories":{},"_nodeVersion":"24.11.0","dependencies":{"i18next-http-backend":"^3.0.2","i18next-browser-languagedetector":"^8.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","ts-jest":"^29.4.5","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.10.0","@types/react":"^19.2.2","i18next-parser":"^9.0.2","@types/react-dom":"^19.2.2","jest-environment-jsdom":"^30.2.0"},"peerDependencies":{"react":"^19.2.0","i18next":"^25.6.1","react-i18next":"^16.2.4"},"_npmOperationalInternal":{"tmp":"tmp/i18n-config_1.2.1_1763387689996_0.5437475506479517","host":"s3://npm-registry-packages-npm-production"}},"1.2.2":{"name":"@ayinza_tech/i18n-config","version":"1.2.2","keywords":[],"author":{"name":"ayinza"},"license":"MIT","_id":"@ayinza_tech/i18n-config@1.2.2","maintainers":[{"name":"ayinza_tech","email":"arthur@ayinza.com"}],"homepage":"https://github.com/sseris/ayinza-i18n-config#readme","bugs":{"url":"https://github.com/sseris/ayinza-i18n-config/issues"},"dist":{"shasum":"ef9bf671c86154ae19e3a7e5bc8a4455a0241478","tarball":"https://registry.npmjs.org/@ayinza_tech/i18n-config/-/i18n-config-1.2.2.tgz","fileCount":19,"integrity":"sha512-hEaDUwFpMMJabXFEz3IH9HvmXkegYrZB5JZ7YCExaZy9L8CSnl3Rj66Q9yD14qfKDeaf96Tl5qwF8KDulmdFlg==","signatures":[{"sig":"MEUCIA3AKMFvbTZDYqAGNTiFa7wwKKjoQWRbKyn7AKeNL0zmAiEA7i+oi46WK5S6TWTYFXzd9c8X8dIWOwTxYSl0H9qvOQ0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":56689},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"b88ab0b60d57af32641c2ea436fbeb504ef276e2","scripts":{"test":"jest","build":"tsc","test:watch":"jest --watch","build:watch":"tsc --watch"},"_npmUser":{"name":"ayinza_tech","email":"arthur@ayinza.com"},"repository":{"url":"git+https://github.com/sseris/ayinza-i18n-config.git","type":"git"},"_npmVersion":"10.8.2","description":"Shared i18next configuration for multiple portals","directories":{},"_nodeVersion":"24.11.0","dependencies":{"i18next-http-backend":"^3.0.2","i18next-browser-languagedetector":"^8.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","ts-jest":"^29.4.5","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.10.0","@types/react":"^19.2.2","i18next-parser":"^9.0.2","@types/react-dom":"^19.2.2","jest-environment-jsdom":"^30.2.0"},"peerDependencies":{"react":"^19.2.0","i18next":"^25.6.1","react-i18next":"^16.2.4"},"_npmOperationalInternal":{"tmp":"tmp/i18n-config_1.2.2_1763391018018_0.5169016922318495","host":"s3://npm-registry-packages-npm-production"}},"1.2.3":{"name":"@ayinza_tech/i18n-config","version":"1.2.3","keywords":[],"author":{"name":"ayinza"},"license":"MIT","_id":"@ayinza_tech/i18n-config@1.2.3","maintainers":[{"name":"ayinza_tech","email":"arthur@ayinza.com"}],"homepage":"https://github.com/sseris/ayinza-i18n-config#readme","bugs":{"url":"https://github.com/sseris/ayinza-i18n-config/issues"},"dist":{"shasum":"5767e318d525300e007f48349eeb67a5d8c28f5a","tarball":"https://registry.npmjs.org/@ayinza_tech/i18n-config/-/i18n-config-1.2.3.tgz","fileCount":19,"integrity":"sha512-+qOBJIzJgxb3JX8W5otKBA28YqAt0HAzPovlZ0oYElRa73QmZukAE5awrRVf6pa7+enlhBT9JqR73ByBFFOGYw==","signatures":[{"sig":"MEUCIA6RcsQ+jOP8MlOMDYwN91iM7DLb3ZShcyloU34ri/kgAiEAislinDqwCy7gObwzL0D74yEW52qKm8yB8SOn/6ZJvOA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57198},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"a558eead58b868417ed6bc901de16cd4d1362f29","scripts":{"test":"jest","build":"tsc","test:watch":"jest --watch","build:watch":"tsc --watch"},"_npmUser":{"name":"ayinza_tech","email":"arthur@ayinza.com"},"repository":{"url":"git+https://github.com/sseris/ayinza-i18n-config.git","type":"git"},"_npmVersion":"10.8.2","description":"Shared i18next configuration for multiple portals","directories":{},"_nodeVersion":"24.11.0","dependencies":{"i18next-http-backend":"^3.0.2","i18next-browser-languagedetector":"^8.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","ts-jest":"^29.4.5","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.10.0","@types/react":"^19.2.2","i18next-parser":"^9.0.2","@types/react-dom":"^19.2.2","jest-environment-jsdom":"^30.2.0"},"peerDependencies":{"react":"^19.1.1","i18next":"^25.6.1","react-i18next":"^16.2.4"},"_npmOperationalInternal":{"tmp":"tmp/i18n-config_1.2.3_1763480045233_0.6090721865153708","host":"s3://npm-registry-packages-npm-production"}},"1.2.4":{"name":"@ayinza_tech/i18n-config","version":"1.2.4","keywords":[],"author":{"name":"ayinza"},"license":"MIT","_id":"@ayinza_tech/i18n-config@1.2.4","maintainers":[{"name":"ayinza_tech","email":"arthur@ayinza.com"}],"homepage":"https://github.com/sseris/ayinza-i18n-config#readme","bugs":{"url":"https://github.com/sseris/ayinza-i18n-config/issues"},"dist":{"shasum":"112a64c4da7e47bdd37e78d420d5e659ac4ef5b5","tarball":"https://registry.npmjs.org/@ayinza_tech/i18n-config/-/i18n-config-1.2.4.tgz","fileCount":19,"integrity":"sha512-9SbjWaChVgRgkhCi15xgWazl2XVKgWWTcYClnvq2IQt91A6D16ohte2R72oxKxYUB+nWTfljBemQTBXqOpbFPA==","signatures":[{"sig":"MEUCIQCBPoTgV+Tcb9J1YK5I+1UuvPCnf9iUXyDLHxwa3agzVAIgeeP4Xl0GvaiJZdZ1MgVIoELG5OTndXl8Ho0ag3oMi/k=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57216},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"273fb088c07f7987197c3ab9c885839346115c0a","scripts":{"test":"jest","build":"tsc","test:watch":"jest --watch","build:watch":"tsc --watch"},"_npmUser":{"name":"ayinza_tech","email":"arthur@ayinza.com"},"repository":{"url":"git+https://github.com/sseris/ayinza-i18n-config.git","type":"git"},"_npmVersion":"10.8.2","description":"Shared i18next configuration for multiple portals","directories":{},"_nodeVersion":"24.11.0","dependencies":{"i18next-http-backend":"^3.0.2","i18next-browser-languagedetector":"^8.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","ts-jest":"^29.4.5","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.10.0","@types/react":"^19.2.2","i18next-parser":"^9.0.2","@types/react-dom":"^19.2.2","jest-environment-jsdom":"^30.2.0"},"peerDependencies":{"react":"^19.1.1","i18next":"^25.6.1","react-i18next":"^16.2.4"},"_npmOperationalInternal":{"tmp":"tmp/i18n-config_1.2.4_1763515760323_0.968033900639184","host":"s3://npm-registry-packages-npm-production"}},"1.2.5":{"name":"@ayinza_tech/i18n-config","version":"1.2.5","keywords":[],"author":{"name":"ayinza"},"license":"MIT","_id":"@ayinza_tech/i18n-config@1.2.5","maintainers":[{"name":"ayinza_tech","email":"arthur@ayinza.com"}],"homepage":"https://github.com/sseris/ayinza-i18n-config#readme","bugs":{"url":"https://github.com/sseris/ayinza-i18n-config/issues"},"dist":{"shasum":"fec128b1fe8a07c624c022bf9f7223dc76e02e27","tarball":"https://registry.npmjs.org/@ayinza_tech/i18n-config/-/i18n-config-1.2.5.tgz","fileCount":19,"integrity":"sha512-BcJNtG7M+rPA6nuSlzmYbZKaYLiwPri8P5ka+qzRVxu8NzOSjpzuyQ2H01zva/XqPjMWIkxB+dTlOGqJngIfiQ==","signatures":[{"sig":"MEUCIQDzKqs5wAc9n3hNsFLEj5ITfaQnCfQwCqy9RQgLGnQogQIgErFofoH8c09AJZ+uLoT84J+pkuYOlvspuL9CkyqlZEA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57216},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"412f3920a7a86a40a896be5eca064fdc332e52f7","scripts":{"test":"jest","build":"tsc","test:watch":"jest --watch","build:watch":"tsc --watch"},"_npmUser":{"name":"ayinza_tech","email":"arthur@ayinza.com"},"repository":{"url":"git+https://github.com/sseris/ayinza-i18n-config.git","type":"git"},"_npmVersion":"10.8.2","description":"Shared i18next configuration for multiple portals","directories":{},"_nodeVersion":"24.11.0","dependencies":{"i18next":"^25.6.1","i18next-http-backend":"^3.0.2","i18next-browser-languagedetector":"^8.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","ts-jest":"^29.4.5","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.10.0","@types/react":"^19.2.2","i18next-parser":"^9.0.2","@types/react-dom":"^19.2.2","jest-environment-jsdom":"^30.2.0"},"peerDependencies":{"react":"^19.1.1","react-i18next":"^16.2.4"},"_npmOperationalInternal":{"tmp":"tmp/i18n-config_1.2.5_1763516812197_0.07697579264961862","host":"s3://npm-registry-packages-npm-production"}},"1.2.6":{"name":"@ayinza_tech/i18n-config","version":"1.2.6","type":"module","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc","build:watch":"tsc --watch","test":"jest","test:watch":"jest --watch"},"keywords":[],"author":{"name":"ayinza"},"license":"MIT","description":"Shared i18next configuration for multiple portals","peerDependencies":{"react":"^19.1.1","react-i18next":"^16.2.4"},"dependencies":{"i18next":"^25.6.1","i18next-browser-languagedetector":"^8.2.0","i18next-http-backend":"^3.0.2"},"devDependencies":{"@types/jest":"^30.0.0","@types/node":"^24.10.0","@types/react":"^19.2.2","@types/react-dom":"^19.2.2","i18next-parser":"^9.0.2","jest":"^30.2.0","jest-environment-jsdom":"^30.2.0","ts-jest":"^29.4.5","typescript":"^5.9.3"},"repository":{"type":"git","url":"git+https://github.com/sseris/ayinza-i18n-config.git"},"bugs":{"url":"https://github.com/sseris/ayinza-i18n-config/issues"},"homepage":"https://github.com/sseris/ayinza-i18n-config#readme","_id":"@ayinza_tech/i18n-config@1.2.6","gitHead":"926d58e552724177325d431cf6c7d762179c199c","_nodeVersion":"24.11.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-NjdJxjWNc3BSf/eHRqca1ttesaUsUTKKQryIQHsXIwJOK6tLB6gww1Y6ZRBGQlKtVPLRv1mWba3ODmBMSUcerw==","shasum":"3182363692032c976f69195b0b6cc0cf9b1eb2de","tarball":"https://registry.npmjs.org/@ayinza_tech/i18n-config/-/i18n-config-1.2.6.tgz","fileCount":19,"unpackedSize":58054,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDBVVqDOzPjr+ZXBkbuhkQKDqxRgs+F71ofRCKK4eVbmAIhAKrYfaXOlHSMmyWpZWLK60AItH5g8+08xqWvmKxBnGKr"}]},"_npmUser":{"name":"ayinza_tech","email":"arthur@ayinza.com"},"directories":{},"maintainers":[{"name":"ayinza_tech","email":"arthur@ayinza.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/i18n-config_1.2.6_1765806223722_0.08652855535697457"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-13T12:18:03.681Z","modified":"2025-12-15T13:43:44.052Z","1.0.0":"2025-11-13T12:18:03.918Z","1.2.0":"2025-11-17T13:33:35.998Z","1.2.1":"2025-11-17T13:54:50.241Z","1.2.2":"2025-11-17T14:50:18.224Z","1.2.3":"2025-11-18T15:34:05.408Z","1.2.4":"2025-11-19T01:29:20.501Z","1.2.5":"2025-11-19T01:46:52.400Z","1.2.6":"2025-12-15T13:43:43.869Z"},"bugs":{"url":"https://github.com/sseris/ayinza-i18n-config/issues"},"author":{"name":"ayinza"},"license":"MIT","homepage":"https://github.com/sseris/ayinza-i18n-config#readme","keywords":[],"repository":{"type":"git","url":"git+https://github.com/sseris/ayinza-i18n-config.git"},"description":"Shared i18next configuration for multiple portals","maintainers":[{"name":"ayinza_tech","email":"arthur@ayinza.com"}],"readme":"# ayinza-i18n-config\n\nShared i18next configuration and formatting utilities for Ayinza portals.\n\nThis package centralizes i18n initialization, sensible defaults, and a set of\nformatters (currency, number, percent, date/time, relative time) that are\nintegrated with i18next's interpolation system. It also provides React hooks\nfor easy consumption in React apps.\n\n## Table of contents\n\n- About\n- Installation\n- Quick start\n- API\n- Configuration\n- Examples\n- Translation extraction\n- Testing\n- Development\n- License\n\n## About\n\nThe library bundles a default i18n configuration (detection, backend, namespaces,\nand formatters) and exposes helpers to initialize i18next, access the global\ni18n instance, and consume localization + formatting helpers in React\ncomponents.\n\nIt is intentionally lightweight and designed to be used as a shared\ndependency across multiple portals that want consistent localization behavior.\n\n## Installation\n\nInstall the package and peer dependencies (peer dependencies are required by\nconsumers and not bundled):\n\n```bash\n# Using npm\nnpm install @ayinza_tech/i18n-config i18next react react-i18next\n\n# Using yarn\nyarn add @ayinza_tech/i18n-config i18next react react-i18next\n```\n\nAlso install the optional runtime backends used by this package (the package\ndeclares them as dependencies):\n\n```bash\nnpm install i18next-http-backend i18next-browser-languagedetector\n```\n\nNote: This package declares `i18next`, `react`, and `react-i18next` as\npeerDependencies — install versions compatible with your app.\n\n## Quick start\n\nInitialize i18n at application startup (for example in `src/main.tsx` or\n`src/index.tsx` in a React app):\n\n```ts\nimport React from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport App from \"./App\";\nimport { initializeI18n } from \"@ayinza_tech/i18n-config\";\n\n// Optional: pass overrides to customize backend, supported languages, or\n// formatters.\ninitializeI18n({\n  config: {\n    portalName: \"My Portal\",\n    backend: {\n      loadPath: \"/locales/{{lng}}/{{ns}}.json\",\n      customHeaders: { \"X-Portal\": \"my-portal\" },\n    },\n    supportedLngs: [\"en\", \"ar\", \"fr\"],\n  },\n})\n  .then(() => {\n    const root = createRoot(document.getElementById(\"root\")!);\n    root.render(<App />);\n  })\n  .catch((err) => console.error(err));\n```\n\nThe function returns the initialized i18next instance and also registers a set\nof custom formatters so you may use them inside translations (interpolation\nformatters) or via the provided hooks.\n\n> **SSR note:** `initializeI18n` touches browser-only globals to set the\n> `dir` attribute. The implementation now guards those calls, but you should\n> still run initialization on the client (e.g., inside a `useEffect` or\n> Next.js `use client` entry point) to ensure detectors and DOM updates work.\n\n## API\n\nTop-level exports (from `src/index.ts`):\n\n- `initializeI18n(options?: I18nInitOptions): Promise<i18n>` — initialize the\n  i18next instance with defaults merged with your overrides.\n- `getI18nInstance(): i18n` — access the i18next singleton.\n- `getFormatters(): I18nFormatters` — get the formatter instance (throws if\n  not initialized).\n- `defaultConfig` — the default configuration object used by\n  `initializeI18n`.\n- `createI18nConfig(partial?: Partial<I18nConfig>)` — returns a fully merged\n  config object without initializing i18next; useful for building configs in\n  build-time tooling or sharing defaults across portals.\n- `createTranslationSnapshot(options: CreateTranslationSnapshotOptions)` —\n  flatten translation JSON trees into a comparable snapshot for diffing.\n- `collectNewTranslationKeys(options: CollectNewTranslationKeysOptions)` —\n  compute keys that were added between two snapshots.\n- `handleNewTranslationKeys(options: HandleNewTranslationKeysOptions)` — push\n  detected keys to a remote endpoint or log them during dry runs.\n- `useFormatting()` — React hook providing formatting helpers bound to the\n  current language.\n- `useI18n()` — combined hook returning both `useTranslation()` props and\n  formatting helpers.\n- Both hooks expect `initializeI18n` to have completed; call initialization in\n  your app bootstrap before rendering components that use them, otherwise\n  `getFormatters()` will throw.\n- Re-exports: `useTranslation`, `Trans`, `Translation` from `react-i18next`.\n\nTypes exported (from `src/types.ts`):\n\n- `I18nConfig` — top-level configuration object shape.\n- `I18nInitOptions` — options for `initializeI18n`.\n- `FormattersConfig`, `LocaleMapping` — configuration shapes for formatters.\n\nFormatters class: `I18nFormatters` provides methods such as:\n\n- `formatCurrency(amount, language, currency?, options?)`\n- `formatNumber(value, language, options?)`\n- `formatPercent(value, language, options?)`\n- `formatDate(date, language, options?)`\n- `formatTime(date, language, options?)`\n- `formatDateTime(date, language, options?)`\n- `formatRelativeTime(value, unit, language, options?)`\n\nThese are already wired into i18next as interpolation formatters named\n`currency`, `number`, `percent`, `date`, `time`, `datetime`, and `relative`.\n\nEvery formatter catches `Intl` errors and falls back to simple strings (for\nexample, returning `INVALID 100` for a bad currency code or `toLocaleString()`\nfor an invalid date). This keeps your UI from crashing, but you may still see\nconsole warnings when supplying malformed input.\n\n## Configuration\n\n`defaultConfig` (summary):\n\n- backend: { loadPath }\n- detection: browser language detection configuration\n- fallbackLng: `en`\n- supportedLngs: `[\"en\",\"ar\",\"fr\",\"es\"]` (override to match your portal to\n  avoid loading unused bundles)\n- defaultNS / ns: namespaces used (feel free to switch to `common` if that is\n  your primary namespace)\n- interpolation.escapeValue: false\n- formatters: default formatter configuration (defaultCurrency `USD`,\n  `fallbackLocale: \"en-US\"` when no locale mapping matches)\n- react: `{ useSuspense: true }` but you can extend it with\n  `bindI18n`/`bindI18nStore` to match your React rendering mode\n\nYou can override only the pieces you need — `initializeI18n` merges defaults\nwith your partial config. If you need a pure helper (no side effects) to\nassemble configs, use `createI18nConfig({ ...overrides })` and feed the result\ninto your own bootstrap logic.\n\nCommon overrides:\n\n- `supportedLngs`: keep this list scoped to the locales your portal actually\n  serves so language detection stays predictable and bundles stay small.\n- `ns` / `defaultNS`: if you share a `common` namespace across portals,\n  consider setting `defaultNS: \"common\"` and trimming the `ns` array.\n- `react`: set `useSuspense: false` for legacy React renderers or provide\n  `bindI18n: \"languageChanged\"` when coordinating with data-fetching layers.\n- `formatters.fallbackLocale`: change this if your organization defaults to a\n  locale other than English; it is used whenever a language code is missing\n  from the locale mapping tables.\n- **Module format:** The published package currently ships as an ES module build\n  (per `tsconfig.json`). If your tooling expects CommonJS, configure your bundler\n  to transpile ESM or consider contributing a dual-build setup.\n\n## Examples\n\nUse translation + formatting together in a React component:\n\n```tsx\nimport React from \"react\";\nimport { useI18n } from \"@ayinza_tech/i18n-config\";\n\nfunction Price({ amount }: { amount: number }) {\n  const { t, formatCurrency } = useI18n();\n\n  return (\n    <div>\n      <h3>{t(\"priceHeading\")}</h3>\n      <p>{formatCurrency(amount)}</p>\n    </div>\n  );\n}\n```\n\nUsing formatters directly (non-React):\n\n```ts\nimport { initializeI18n, getFormatters } from \"@ayinza_tech/i18n-config\";\n\nasync function start() {\n  await initializeI18n();\n  const fmt = getFormatters();\n  console.log(fmt.formatCurrency(19.99, \"en\", \"USD\"));\n}\n```\n\nUsing interpolation in translation strings (example `en/common.json`):\n\n```json\n{\n  \"price\": \"{{value, currency}}\"\n}\n```\n\nThen `t('price', { value: 19.99, currency: 'EUR' })` will use the registered\n`currency` formatter.\n\n## Integrating Across Multiple Portals\n\nWhen sharing this package across portals, keep initialization centralized so\neach shell bootstraps consistently:\n\n1. Create a thin wrapper (e.g., `packages/i18n/client.ts`) that calls\n   `initializeI18n` with portal-specific overrides such as namespace lists or\n   branding headers.\n2. Import only that wrapper from each portal entry point to keep behavior\n   aligned and avoid forgetting required detectors/backends.\n3. Re-export helpers (`useI18n`, `getFormatters`) from your shell layer so\n   downstream micro frontends consume the same singleton instance.\n4. For SSR/Next.js, run `initializeI18n` inside client components or a `useEffect`\n   guard to allow detectors to access browser APIs, then hydrate shared hooks.\n\nExample shared bootstrap that portals can reuse:\n\n```ts\n// packages/i18n/bootstrap.ts\nimport {\n  initializeI18n,\n  getFormatters,\n  useI18n,\n} from \"@ayinza_tech/i18n-config\";\n\nexport async function setupPortalI18n(portalName: string) {\n  await initializeI18n({\n    config: {\n      portalName,\n      backend: {\n        loadPath: `/locales/${portalName}/{{lng}}/{{ns}}.json`,\n      },\n      supportedLngs: [\"en\", \"fr\", \"sw\"],\n    },\n  });\n\n  return {\n    i18n: getFormatters(),\n    useI18n,\n  };\n}\n\n// portal-a/src/main.tsx\nimport { setupPortalI18n } from \"@ayinza/portal-shared/i18n\";\n\nsetupPortalI18n(\"portal-a\").then(() => {\n  // mount React app here, all child components can call useI18n()\n});\n```\n\nThis pattern keeps each portal lightweight while ensuring updates to the core\nlocalization stack propagate everywhere by upgrading just this package.\n\n## Translation extraction\n\nThe package now ships with light wrappers around\n[`i18next-parser`](https://github.com/i18next/i18next-parser) so every portal\ncan reuse the same extraction defaults and push workflow:\n\n1. **Config helper.** Create `i18next-parser.config.mjs` (or `.cjs`) that simply\n   exports `createI18nextParserConfig({ /* overrides */ })`. The helper sets\n   consistent defaults (lexers, separators, indentation, `createOldCatalogs`,\n   etc.) so every portal parses sources the same way.\n\n   ```ts\n   // i18next-parser.config.mjs\n   import { createI18nextParserConfig } from \"@ayinza_tech/i18n-config\";\n\n   export default createI18nextParserConfig({\n     input: [\"src/**/*.{ts,tsx}\"],\n     locales: [\"en\"],\n     output: \"locales/$LOCALE/$NAMESPACE.json\",\n   });\n   ```\n\n2. **Detect new keys.** Capture a snapshot before and after running the parser\n   (usually for the default locale) by loading your locale JSON and passing it\n   to `createTranslationSnapshot`, then call `collectNewTranslationKeys` to\n   compute the delta.\n\n   ```ts\n   import { readFile } from \"node:fs/promises\";\n   import path from \"node:path\";\n   import {\n     createTranslationSnapshot,\n     collectNewTranslationKeys,\n     handleNewTranslationKeys,\n   } from \"@ayinza_tech/i18n-config\";\n\n   const localesRoot = path.resolve(\"locales\");\n   const namespaces = [\"translation\", \"common\"];\n\n   async function loadNamespaces(locale: string) {\n     const entries = await Promise.all(\n       namespaces.map(async (namespace) => {\n         const filePath = path.join(localesRoot, locale, `${namespace}.json`);\n         const raw = await readFile(filePath, \"utf8\");\n         return [namespace, JSON.parse(raw) as Record<string, unknown>];\n       })\n     );\n\n     return Object.fromEntries(entries) as Record<\n       string,\n       Record<string, unknown>\n     >;\n   }\n\n   const before = createTranslationSnapshot({\n     locale: \"en\",\n     namespaces: await loadNamespaces(\"en\"),\n   });\n\n   // Run `npx i18next --config i18next-parser.config.mjs \"src/**/*.{ts,tsx}\"`\n\n   const after = createTranslationSnapshot({\n     locale: \"en\",\n     namespaces: await loadNamespaces(\"en\"),\n   });\n\n   const newKeys = collectNewTranslationKeys({ previous: before, next: after });\n   ```\n\n3. **Push or log.** Pass the detected keys to `handleNewTranslationKeys` to run\n   a dry-run or POST them to your translation management service. Configure the\n   helper with portal-specific metadata so CI logs stay readable.\n\n   ```ts\n   await handleNewTranslationKeys({\n     newKeys,\n     pushConfig: {\n       portalName: \"admin-shell\",\n       pushUrl: process.env.TRANSLATION_PUSH_URL,\n       authorizationToken: process.env.TRANSLATION_PUSH_TOKEN,\n       dryRun: process.env.CI === \"true\" && process.env.DRY_RUN === \"true\",\n     },\n   });\n   ```\n\nIf `pushUrl` is omitted or `dryRun` is `true`, the helper only logs detected\nkeys. Provide a custom `fetchImpl` via `ParserPushConfig` when running on Node\nversions older than 18 (which lack `global.fetch`).\n\n## Testing\n\nThere are unit tests for `I18nFormatters` (see `src/formatters.test.ts`) and\nfor the config merge helper (see `src/config.test.ts`). Run tests with the\nprovided npm scripts:\n\n```bash\nnpm test\n# watch mode during development\nnpm run test:watch\n```\n\nNote: this repository includes Jest devDependencies configured for TypeScript.\n\n## Development\n\n- Build: `npm run build` (compiles to `dist/` using `tsc`)\n- Watch: `npm run build:watch`\n- Test: `npm test` or `npm run test:watch`\n\nIf you intend to contribute, please run tests and add coverage for new\nfeatures.\n\n## License\n\nMIT — see the `LICENSE` file in this repository.\n\n## Next steps & suggestions\n\n- Add CI (GitHub Actions) to run tests and build on push/PR.\n- Add usage examples / Storybook snippets for React components that depend on\n  formatting.\n- Consider publishing with changelog and semantic-release for automated\n  releases.\n- Allow consumers to provide a custom logger/debug handler so initialization\n  logs can be routed through their monitoring stack instead of console.\n","readmeFilename":"README.md"}