{"id":20509549,"url":"https://github.com/aminnairi/node-translation","last_synced_at":"2026-04-12T00:02:32.236Z","repository":{"id":52934083,"uuid":"357256622","full_name":"aminnairi/node-translation","owner":"aminnairi","description":"Multi-lingual translation library.","archived":false,"fork":false,"pushed_at":"2021-04-17T17:12:24.000Z","size":290,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"next","last_synced_at":"2025-02-16T19:48:51.247Z","etag":null,"topics":["alpine","alpinejs","express","expressjs","hyperapp","i18n","internationalization","l10n","language","lingual","locale","localization","preact","react","svelte","translate","translation","translator"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@aminnairi/translation","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aminnairi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null}},"created_at":"2021-04-12T16:03:32.000Z","updated_at":"2022-11-12T14:56:14.000Z","dependencies_parsed_at":"2022-08-20T16:50:40.687Z","dependency_job_id":null,"html_url":"https://github.com/aminnairi/node-translation","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aminnairi%2Fnode-translation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aminnairi%2Fnode-translation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aminnairi%2Fnode-translation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aminnairi%2Fnode-translation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aminnairi","download_url":"https://codeload.github.com/aminnairi/node-translation/tar.gz/refs/heads/next","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242112221,"owners_count":20073549,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alpine","alpinejs","express","expressjs","hyperapp","i18n","internationalization","l10n","language","lingual","locale","localization","preact","react","svelte","translate","translation","translator"],"created_at":"2024-11-15T20:25:22.393Z","updated_at":"2026-04-12T00:02:32.186Z","avatar_url":"https://github.com/aminnairi.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @aminnairi/translation\n\nMulti-lingual translation library.\n\n[![Build](https://github.com/aminnairi/node-translation/actions/workflows/build.yaml/badge.svg?branch=latest)](https://github.com/aminnairi/node-translation/actions/workflows/build.yaml) [![Tests](https://github.com/aminnairi/node-translation/actions/workflows/tests.yaml/badge.svg?branch=latest)](https://github.com/aminnairi/node-translation/actions/workflows/tests.yaml) [![Code Style](https://github.com/aminnairi/node-translation/actions/workflows/codestyle.yaml/badge.svg?branch=latest)](https://github.com/aminnairi/node-translation/actions/workflows/codestyle.yaml) [![Audit](https://github.com/aminnairi/node-translation/actions/workflows/audit.yaml/badge.svg?branch=latest)](https://github.com/aminnairi/node-translation/actions/workflows/audit.yaml)\n\n[![NPM Badge](https://badgen.net/npm/v/@aminnairi/translation/)](https://www.npmjs.com/package/@aminnairi/translation) [![Bundle size](https://badgen.net/bundlephobia/minzip/@aminnairi/translation)](https://bundlephobia.com/result?p=@aminnairi/translation) [![Tree shaking support](https://badgen.net/bundlephobia/tree-shaking/@aminnairi/translation)](https://bundlephobia.com/result?p=@aminnairi/translation)\n\n## Usage\n\n### General\n\n```javascript\n\"use strict\";\n\nconst translate = Translation.create({\n  language: navigator.language || \"\",\n  translations: {\n    \"Hello {person}, did you finish the {project} project?\": {\n      \"fr-FR\": \"Salut {person}, est-ce que tu as fini le projet {project} ?\",\n      \"es-ES\": \"Hola {person}, ¿has terminado el proyecto {project}?\"\n    }\n  }\n});\n\nconst person = \"John DOE\";\nconst project = \"TOPSECRET\";\nconst translation = translate`Hello ${person}, did you finish the ${project} project?`;\n```\n\n### Browser\n\n#### ECMAScript Module\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003cscript type=\"module\"\u003e\n    import {Translation} from \"https://unpkg.com/@aminnairi/translation/module\";\n  \u003c/script\u003e\n\u003c/html\u003e\n```\n\n#### Classic\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003cscript src=\"https://unpkg.com/@aminnairi/translation/browser\"\u003e\u003c/script\u003e\n  \u003cscript type=\"module\"\u003e\n    \"use strict\";\n\n    const {Translation} = window[\"@aminnairi/translation\"];\n  \u003c/script\u003e\n\u003c/html\u003e\n```\n\n### Node.js\n\n```console\n$ npm install @aminnairi/translation\n```\n\n#### ECMAScript Module\n\n```javascript\nimport {Translation} from \"@aminnairi/translation\";\n```\n\n#### CommonJS\n\n```javascript\nconst {Translation} = require(\"@aminnairi/translation\");\n```\n\n### Deno\n\n```javascript\nimport {Translation} from \"https://unpkg.com/@aminnairi/translation/module\";\n```\n\n### Frameworks\n\n#### Alpine.js\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003cbody\u003e\n    \u003cdiv x-data=\"data()\"\u003e\n      \u003cp x-text=\"translate`Hello ${person}, did you finish the ${project} project?`\"\u003e\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cscript src=\"https://unpkg.com/alpinejs\"\u003e\u003c/script\u003e\n    \u003cscript src=\"https://unpkg.com/@aminnairi/translation/browser\"\u003e\u003c/script\u003e\n    \u003cscript\u003e\n      \"use strict\";\n\n      const {Translation} = window[\"@aminnairi/translation\"];\n\n      const translate = Translation.create({\n        language: navigator.language || \"\",\n        translations: {\n          \"Hello {person}, did you finish the {project} project?\": {\n            \"fr-FR\": \"Salut {person}, est-ce que tu as fini le projet {project} ?\",\n            \"es-ES\": \"Hola {person}, ¿has terminado el proyecto {project}?\"\n          }\n        }\n      });\n\n      const data = () =\u003e ({\n        translate,\n        person: \"John DOE\",\n        project: \"TOPSECRET\"\n      });\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n#### React\n\n```javascript\nimport React, {useState, useCallback} from \"react\";\nimport {render} from \"react-dom\";\nimport {Translation} from \"@aminnairi/translation\";\n\nconst App = () =\u003e {\n  const [person] = useState(\"John DOE\");\n  const [project] = useState(\"TOPSECRET\");\n  const translate = useCallback(Translation.create({\n    language: navigator.language || \"\",\n    translations: {\n      \"Hello {person}, did you finish the {project} project?\": {\n        \"fr-FR\": \"Bonjour {person}, avez-vous terminé le project {project} ?\",\n        \"es-ES\": \"Hola {person}, ¿has terminado el proyecto {project}?\"\n      }\n    }\n  }), [Translation.create, navigator.language]);\n\n  return \u003cp\u003e{translate`Hello ${person}, did you finish the ${project} project?`}\u003c/p\u003e\n};\n\nrender(\u003cApp /\u003e, document.getElementById(\"app\"));\n```\n\n#### Preact\n\n```javascript\nimport {h, render} from \"preact\";\nimport {useState, useCallback} from \"preact/hooks\";\nimport {Translation} from \"@aminnairi/translation\";\n\nconst App = () =\u003e {\n  const [person] = useState(\"John DOE\");\n  const [project] = useState(\"TOPSECRET\");\n  const translate = useCallback(Translation.create({\n    language: navigator.language || \"\",\n    translations: {\n      \"Hello {person}, did you finish the {project} project?\": {\n        \"fr-FR\": \"Bonjour {person}, avez-vous terminé le project {project} ?\",\n        \"es-ES\": \"Hola {person}, ¿has terminado el proyecto {project}?\"\n      }\n    }\n  }), [Translation.create, navigator.language]);\n\n  return \u003cp\u003e{translate`Hello ${person}, did you finish the ${project} project?`}\u003c/p\u003e\n};\n\nrender(\u003cApp /\u003e, document.getElementById(\"root\"));\n```\n\n#### React Native\n\n```javascript\nimport React, {useCallback, useState} from 'react';\nimport { StyleSheet, Text, View, Platform, NativeModules } from 'react-native';\nimport {Translation} from \"@aminnairi/translation\";\n\nconst deviceLanguage = Platform.OS === 'ios'\n  ? NativeModules.SettingsManager.settings.AppleLocale ||\n    NativeModules.SettingsManager.settings.AppleLanguages[0] // iOS 13\n  : NativeModules.I18nManager.localeIdentifier;\n\nexport default function App() {\n  const [person] = useState(\"John DOE\");\n  const [project] = useState(\"TOPSECRET\");\n  const translate = useCallback(Translation.create({\n    language: deviceLanguage,\n    translations: {\n      \"Hello {person}, did you finish the {project} project?\": {\n        \"fr-FR\": \"Bonjour {person}, avez-vous terminé le project {project} ?\",\n        \"es-ES\": \"Hola {person}, ¿has terminado el proyecto {project}?\"\n      }\n    }\n  }), [Translation.create, deviceLanguage]);\n\n  return (\n    \u003cView style={styles.container}\u003e\n      \u003cText\u003e{translate`Hello ${person}, did you finish the ${project} project?`}\u003c/Text\u003e\n    \u003c/View\u003e\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: '#fff',\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n});\n```\n\n#### Hyperapp\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003cbody\u003e\n    \u003cdiv id=\"app\"\u003e\u003c/div\u003e\n    \u003cscript type=\"module\"\u003e\n      import {h, app, text} from \"https://unpkg.com/hyperapp\";\n      import {Translation} from \"https://unpkg.com/@aminnairi/translation/module\";\n\n      const translate = Translation.create({\n        language: navigator.language || \"\",\n        translations: {\n          \"Hello {person}, did you finish the {project} project?\": {\n            \"fr-FR\": \"Salut {person}, est-ce que tu as fini le projet {project} ?\",\n            \"es-ES\": \"Hola {person}, ¿has terminado el proyecto {project}?\"\n          }\n        }\n      });\n\n      app({\n        init: {person: \"John DOE\", project: \"TOPSECRET\", translate},\n        node: document.getElementById(\"app\"),\n        view: ({person, project, translate}) =\u003e h(\"main\", {}, [\n          h(\"p\", {}, text(translate`Hello ${person}, did you finish the ${project} project?`))\n        ])\n      }); \n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n#### Svelte\n\n```html\n\u003cscript\u003e\n  import {Translation} from \"@aminnairi/translation\";\n\n  const person = \"John DOE\";\n  const project = \"TOPSECRET\";\n\n  const translate = Translation.create({\n    language: navigator.language || \"\",\n    translations: {\n      \"Hello {person}, did you finish the {project} project?\": {\n        \"fr-FR\": \"Salut {person}, est-ce que tu as fini le projet {project} ?\",\n        \"es-ES\": \"Hola {person}, ¿has terminado el proyecto {project}?\"\n      }\n    }\n  });\n\u003c/script\u003e\n\n\u003cmain\u003e\n  \u003cp\u003e{translate`Hello ${person}, did you finish the ${project} project?`}\u003c/p\u003e\n\u003c/main\u003e\n```\n\n#### Express.js\n\n```javascript\nimport express from \"express\";\nimport {Translation} from \"@aminnairi/translation\";\n\nconst server = express();\n\nserver.use((request, response, next) =\u003e {\n  request.translate = Translation.create({\n    language: request.acceptsLanguages()[0] || \"\",\n    translations: {\n      \"Hello {person}, did you finish the {project} project?\": {\n        \"fr-FR\": \"Salut {person}, est-ce que tu as fini le projet {project} ?\",\n        \"es-ES\": \"Hola {person}, ¿has terminado el proyecto {project}?\"\n      }\n    }\n  });\n\n  next();\n});\n\nserver.get(\"/\", (request, response) =\u003e {\n  const person = \"John DOE\";\n  const project = \"TOPSECRET\";\n  const translation = request.translate`Hello ${person}, did you finish the ${project} project?`;\n\n  response.json({success: true, data: translation});\n});\n\nserver.listen(8080, \"0.0.0.0\", () =\u003e {\n  console.log(\"server is listening\");\n});\n```\n\n## API\n\n```javascript\n/**\n * @description Create a translation function to help you with your internationalization\n * @returns {Function} A tag function that when called translate the text\n * @example\n * const translate = Translation.create({language, translations});\n */\nconst translate = Translation.create({\n  /**\n   * This is the language that is grabbed from the user, an OS setting, the browser, etc...\n   * @type {string} The language with variant (or not) that will be used to translate any text\n   * @example\n   * Translation.create({language = \"fr-FR\", translations});\n   * Translation.create({language = \"fr\", translations});\n   * Translation.create({language = \"fr_FR\", translations});\n   * Translation.create({language: navigator.language || \"en-US\", translations});\n   */\n  language: \"fr-FR\",\n  /**\n   * @description These are the translations texts that will also be used on your app\n   * @type {object} An objet of all the texts to translate with their translations\n   */\n  translations: {\n    // If the language does not match either one of these languages, it will return this text\n    \"Hello\": {\n      // If the language is either \"fr-FR\", \"fr-fr\", \"fr_FR\", \"fr_fr\" or \"fr\" it will match\n      \"fr-FR\": \"Bonjour\",\n      // If the language is either \"es-ES\", \"es-es\", \"es_ES\", \"es_es\" or \"es\" it will match\n      \"es-ES\": \"Hola\"\n    },\n    // Text can have placeholder too, \n    \"Hi {person}!\": {\n      // The translated text can reference these placeholders\n      \"fr-FR\": \"Salut {person} !\",\n      // See down bellow for examples\n      \"es-ES\": \"¡Hola {person}!\"\n    },\n    // You can even repeat a placeholder multiple times\n    \"Do you love {bookName}? I love {bookName}, and {anotherBookName} also!\": {\n      // Translations can put the placeholder in any order, anywhere in the text\n      \"fr-FR\": \"Tu aimes {bookName} ? J'aime {bookName} et {anotherBookName} aussi !\",\n      // So that you can adapt the translation to the language idioms\n      \"es-ES\": \"¿Te gusta {bookName}? Me encantan {bookName} y {anotherBookName} también.\"\n    }\n  }\n});\n\nconst person = \"Jake\";\nconst bookName = \"Enemy of Terror\";\nconst anotherBookName = \"The Storm Oath\";\n\ntranslate`Hello`;\n// \"Bonjour\"\n\ntranslate`Hi ${person}!`;\n// \"Salut Jake !\"\n\ntranslate`Hello ${person}!`;\n// \"Hello Jake!\" (untranslated because no match found)\n\ntranslate`Do you love ${bookName}? I love ${bookName}, and ${anotherBookName} also!`;\n// \"Tu aimes Enemy of Terror ? J'aime Enemy of Terror et The Storm Oath aussi !\"\n```\n\n## Requirements\n\n- [Node](https://nodejs.org/)\n\n## Pros\n\n### Functional\n\nThis library has been made with functional programming in mind. The main goal is to disminish the amount of side-effects it can create, reduce the chance to introduce bugs and enhance its reliability.\n\n### Scalable\n\nYou can start small and have a simple translation function that holds all of your translations, or you can split your translations in separated files and import them just as you would import any other JavaScript module. Or even create small translations functions that follow your components architecture. You handle this part of the work, you are in control of how you want your translations to be managed, the library will scale with you.\n\n### Testable\n\nThis library has been tested to reach the maximum coverage possible. So that you can focus on your translations and less on the tool itself. You are even encouraged to create small translation functions that you can then later test easily, no mocks to be made, no ugly hack to simulate the file system. Functions are the most easy piece of software you can test and this library aims at reducing this friction as well so that you can have an app reliable in both its translations and its features.\n\n### Lighweight\n\nThis library is one of the smallest you can find in the package ecosystem of translation. Yet, it can easily be used in many frameworks and libraries, or even just plain JavaScript and help you make your website internationalized quickly and efficiently.\n\n### Reusable\n\nSome library have taken the direction of using key identifiers for referencing translations throughout the sources. Keys don't scale well because you need to come out with a new name that is relevant each time. Why not use the text itself? By doing that, you reduce the friction and the speed at which a text can be translated and focus on the translation itself. Plus, you can't make the mistake of writing the same key twice for the same translation since you are using the text itself.\n\n### Flexible\n\nWhether the locale identifier comes from the operating system, a HTTP header or the user selection, this library will help you with your translation. It does not assume where to find the locale: you do. You are in total control with this library, yet it is powerful enough to help you translate your application in a single responsibility principle manner.\n\n### Dynamic\n\nUsing variables is part of making our applications dynamic and is at the heart of any interesting application out there. But what about dynamic texts and translations? Are you willing to spend some time making three translations for only one text because it has some dynamic content? That is too much work and focus on the implementation, and less on the translation itself. With this library, you only have one text, and one (or more) translation for the language you want to support. That's it. Less work for you, means more time for interesting features.\n\n### Polymorphic\n\nSince this library is written in pure JavaScript, you can easily integrate it with almost any technology, whether it is a front-end framework or library, or a back-end web server library or framework. This library will find its way to help you get the most of internationalization. You can also open an issue if you want your favorite framework/library to be on the documentation examples and I'll try my best to provide with an example for that, or you can also help me make the documentation better!\n\n### Smart\n\nIf you are supporting a multi-lingual website with many variants, you'll find your happinness in this library. It supports working with language and variants nicely and offers intelligent fallback when a variant is not found. You can have a translation for `fr` variants and have it work as a fallback for browsers that supports `fr-CH` or `fr-CA` variants. You can even have a text for a `fr-CA` variant and still make it work for browser that support a wider `fr` locale. And if you need specific idioms for specific variants, you can still have both `fr-CA` and `fr-CH` and they will work together nicely. All this processing is already baked in the library and you only have to ever focus on the translation and nothing else.\n\n### Isomorphic\n\nYou just found a wonderful translation library for your back-end server app and want to use it on another front-end application. Same tool, different environment let's you focus more on the translation and less on the implementation details Since it does not rely on any Node.js specific API, only JavaScript standards. You want to store your translations on a file? You can! Just read them and feed them to the `Translation.create` builder. That's it. You want to fetch your translations from the internet on a front-end application? Why not! Just feed them again and you are good to go. You can even use this library with React Native with absolutely no friction!\n\n### Helpful\n\nWhen was the last time you saw helpful error messages in the console? Even if you make mistakes, nice and gentle errors are thrown with emphasis on precision and usefulness. Plus, it makes you gain some precious time by not having to traceback all the stack frames to search for the reason why it does not work. Never fear the errors again! \n\n### Semantic Versioning\n\nThis library highly relies on a strict semantic versioning of its package. If you upgrade to a minor version, it will always be backward compatible change. If you upgrade to a patch version, it will always be about a bug fixed and if you upgrade to a major version, it will mean that it will probably break the current API. Reliability and trust are at the heart of this library and no amout of popularity will change that.\n\n## Cons\n\n### Vue\n\nThis library does not integrate well with Vue.js. Although I was a long-time Vue afficionado, it does not get well with template tag function, which is the heart of this library. The main reason is that you simply cannot evaluate template tag function in a Vue interpollation (double curly braces). There are way better libraries that make the work of translating a Vue app and I'm 100% sure you'll find something that will suit your needs.\n\n### Performance\n\nAlthough performance has not been the main goal of this library, it should be pretty fast out of the box. Try not to store all of your translations in one object but rather have your translations splitted by pages or domains so that it will be less payload to get from the client-side. Less payload also means less to search for the translation function!\n\n### Text as key\n\nHaving the text as a key has the benefit of introducing less keys, and is a more human way of seeing the translations. But this comes at the cost of making the translation sensible. If you mistakenly add a character, the translation will not match the one you wanted and will fallback to the text itself non-translated. This can be seen as both a pro or a con. You are sacrificing that in the benefit of having a much easier time to integrate dynamic translations. Be wise and do test your views whether it is manually or automatically!\n\n### TypeScript\n\nThis library has no intent on being ever written in TypeScript, although you are free to come up with typings for this library if you want. Using text as keys means that TypeScript won't be of any help in helping you autocomplete or type check your texts. Plus, runtime type checking is already done for errors that come up in both development or production environments.\n\n## Issues\n\nSee [`issues`](../../issues).\n\n## Contributing\n\nSee [`CONTRIBUTING.md`](./CONTRIBUTING.md).\n\n## Changelog\n\nSee [`CHANGELOG.md`](./CHANGELOG.md).\n\n## License\n\nSee [`LICENSE`](./LICENSE).\n\n## Security Policy\n\nSee [`SECURITY.md`](./SECURITY.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faminnairi%2Fnode-translation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faminnairi%2Fnode-translation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faminnairi%2Fnode-translation/lists"}