{"id":15156621,"url":"https://github.com/puppetlabs/clj-i18n","last_synced_at":"2025-12-12T01:21:56.787Z","repository":{"id":30803299,"uuid":"34360399","full_name":"puppetlabs/clj-i18n","owner":"puppetlabs","description":"Clojure i18n library and utilities","archived":false,"fork":false,"pushed_at":"2023-06-22T16:00:54.000Z","size":187,"stargazers_count":24,"open_issues_count":2,"forks_count":20,"subscribers_count":145,"default_branch":"main","last_synced_at":"2025-01-29T02:05:39.123Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/puppetlabs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-04-22T00:59:04.000Z","updated_at":"2024-08-19T05:35:46.000Z","dependencies_parsed_at":"2024-06-21T16:45:27.815Z","dependency_job_id":"ba8a9d43-9970-409c-bf84-965e87c00273","html_url":"https://github.com/puppetlabs/clj-i18n","commit_stats":null,"previous_names":["puppetlabs/i18n"],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/puppetlabs%2Fclj-i18n","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/puppetlabs%2Fclj-i18n/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/puppetlabs%2Fclj-i18n/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/puppetlabs%2Fclj-i18n/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/puppetlabs","download_url":"https://codeload.github.com/puppetlabs/clj-i18n/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237172536,"owners_count":19266700,"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":[],"created_at":"2024-09-26T19:24:04.685Z","updated_at":"2025-10-19T17:30:50.813Z","avatar_url":"https://github.com/puppetlabs.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# i18n\n\nA Clojure library and leiningen plugin to make i18n easier. Provides\nconvenience functions to access the JVM's localization facilities and\nautomates managing messages and resource bundles. The tooling for\ntranslators uses [GNU gettext](http://www.gnu.org/software/gettext/), so\nthat translators can work with `.po` files which are widely used and for\nwhich a huge amount of tooling exists.\n\nThe `main.clj` and `example/program` in this repo contain some simple code\nthat demonstrates how to use the translation functions. Before you can use\nit, you need to run `make` to generate the necessary\n`ResourceBundles`. After that, you can use `lein run -m puppetlabs.i18n.main`\nor `LANG=de_DE lein run -m puppetlabs.i18n.main` to look at English and German output.\n\n## Developer usage\n\nAny Clojure code that needs to generate human-readable text must use the\nfunctions `puppetlabs.i18n.core/trs` and `puppetlabs.i18n.core/tru` to do\nso. Use `trs` for messages that should be formatted in the system's locale,\nfor example log messages, and `tru` for messages that will be shown to the\ncurrent user, for example an error that happened processing a web request.\n\nWhen you require `puppetlabs.i18n.core` into your namespace, you *must*\ncall it either `trs`/`tru`/`trun`/`trsn` or\n`i18n/trs`/`i18n/tru`/`i18n/trun`/`i18n/trsn` (these are the names that\n`xgettext` will look for when it extracts strings) Typically, you\nwould have this in your namespace declaration\n\n    (ns puppetlabs.myproject\n      (:require [puppetlabs.i18n.core :as i18n :refer [trs trsn tru trun]]))\n\nYou use `trs`/`tru` very similar to how you use `format`, except that the\nformat string must be a valid\n[`java.text.MessageFormat`](https://docs.oracle.com/javase/8/docs/api/java/text/MessageFormat.html)\npattern. Note that these patterns offer support for localized formatting;\nsee the Javadocs for details. For example, you would write\n\n    (println (trs \"It takes {0} software engineers {1} hours to change a light bulb\" 3 9))\n\n`trsn`/`trun` are similar to `trs`/`tru` except that they support pluralization\nof strings.  The first argument is the singular version of the string, the second\nargument must be the plural form of the string.  The third argument is the count value\nto determine the level of pluralization.  Any additional arguments will be used for additional formatting\n\n    (println (trsn \"We found one cute puppy\" \"We found {0} cute puppies\" 5))\n\nUse `format` to keep whitespace out of translations:\nReplace the following string\n\n    (str \"Verification of client \"\n      client\n      \" provided by HTTP header \"\n      header\n      \"\\nSee documentation for details\\n\")\n\nwith something like:\n\n    (format \"%s\\n%s\\n\"\n      (trs \"Verification of client {0} provided by HTTP header {1}\" client header)\n      (trs \"See documentation for details\"))\n\nNote that `trs` and `tru` work exactly the same way as `format` in clojure, and\nthat it does not concatenate the way `str` does. If used this way, `trs` and\n`tru` will only recognize the first string given and no exception will be thrown.\n\n### How to find the Strings\n\nHere is a crappy Ruby script that you can point at a Clojure source tree to find *most* of the strings that will need to be translated:\n\nhttps://github.com/cprice404/stringtracker/blob/master/getstrings.rb\n\n### Comments for translators\n\nIt is sometimes useful to tell the translator something about the message;\nyou can do that by preceding the message string in the`trs`/`tru`\ninvocation with a comment; in the above example you might want to say\n\n    ;; This is really just a silly example message. It gets the following\n    ;; arguments:\n    ;; 0 : number of software engineers (an integer)\n    ;; 1 : number of hours (also an integer)\n    (println (trs \"It takes {0} software engineers {1} hours to change a light bulb\" 3 9))\n\nThe comment will be copied to `\u003cproject-name\u003e.pot` together with the actual\nmessage so that translators have some context on what they are working\non. Note that such comments must be immediately preceding the string that\nis the message. When you write\n\n    ;; No translator will see this\n    (trs\n      \"A message on another line\")\n\nthe comments do *not* get extracted into `\u003cproject-name\u003e.pot`.\n\n### Single quotes in messages\n\nSingle quotes have a special meaning in\n[`java.text.MessageFormat`](https://docs.oracle.com/javase/8/docs/api/java/text/MessageFormat.html)\npatterns and need to be escaped with another single quote:\n\n    ;; The following will produce \"Hes going to the store\"\n    (trs \"He's going to the store\")\n\n    ;; You may want to supply a comment for devs and\n    ;; translators to make sure the quoting is preserved.\n    ;; The following will produce \"He's going to the store\"\n    (trs \"He''s going to the store\")\n\n### Separating message extraction from translation\n\nIn some cases, messages need to be generated separately from when they're\ntranslated; this is common in specialized `def` forms or when defining a\nconstant for reuse. In that case, use the `mark` macro to mark strings for\nxgettext extraction, and the standard `trs`/`tru` at the translation site.\n\n### Development tools\n\nExtracting messages and building ResourceBundles requires the command line tools\nfrom [GNU gettext](https://www.gnu.org/software/gettext/) which you will have to\ninstall manually.\n\nIf you are using Homebrew on OSX, run `brew install gettext`. OSX provides the\nBSD gettext library by default and because of that the Homebrew formula for\n`gettext` is keg-only. keg-only formulas are not symlinked. This can be remedied\nby running `brew link gettext --force`.\n\nOn Red Hat-based operating systems, including Fedora, install gettext via\n`yum install gettext`\n\n### Project setup\n\n[![Clojars Project](https://img.shields.io/clojars/v/puppetlabs/i18n.svg)](https://clojars.org/puppetlabs/i18n)\n\n1. In your `project.clj`, add `[puppetlabs/i18n \"0.9.0\"]` to your project's\n   :plugins and :dependencies vectors (without the version number in\n   :dependencies if your project uses clj-parent). Also add\n   ```\n   :uberjar-merge-with {\"locales.clj\"  [(comp read-string slurp)\n                                        (fn [new prev]\n                                          (if (map? prev) [new prev] (conj prev new)))\n                                        #(spit %1 (pr-str %2))]}\n   :prep-tasks [[\"i18n\" \"make\"] \"javac\" \"compile\"]\n   ```\n   to merge in the translation locales.clj from upstream projects and make sure the plugin is\n   invoked.\n2. Run `lein i18n init`. This will\n   * put a `Makefile.i18n` into `dev-resources/` in your project and include it\n     into an existing toplevel `Makefile` resp. create a new one that does that.\n     You should check these files into you source control system.\n   * put scripts for comparing and updating PO \u0026 POT files in\n     `dev-resources/i18n/bin`. (These scripts and the Makefile.i18n are updated to\n     include your project name, so that the POT file will be named after your project.)\n     These are used by [the clj-i18n CI job][ci-job]\n     and can be ignored (they are added to the project's .gitignore file).\n3. **If there are namespaces/packages in your project with names which do not\n   start with a prefix derived from the project name:** you'll need to list all\n   of your namespaces/package name prefixes in the `PACKAGES` variable in the\n   top level `Makefile` before the inclusion of the `dev-resources/Makefile.i18n`\n4. Add a job using [CI job configs' i18n-clj template][ci-job] to your project's\n   CI pipelines. This job will automatically update the POT file when\n   externalized strings are added or changed in the project.\n\n[ci-job]: https://github.com/puppetlabs/ci-job-configs/blob/master/resources/job-templates/i18n-clj.yaml\n\nThis setup will ensure that compiling your project will also regenerate the Java\n`ResourceBundle` classes that your code needs to do translations.\n\nYou can manually regenerate these files by running `make i18n`. Additional\ninformation about the Make targets is available through running `make help`.\n\n**Note: `make i18n` will fail if you don't have at least one string wrapped with a translation function, i.e. trs or tru.**\n\nThe i18n tools maintain files in three directories:\n  * message catalogs in `locales/`\n  * compiled translations in `resources/`\n  * temporary files in the project root `/`, for example `/mp-e`\n\nYou should check the files in `locales/` into source control, but not the ones\n in `resources/` or the `mp-*` files. A sample `.gitignore` for a project might\n look something like:\n\n ```\n # Ignore these files for clj-i18n\n /resources/example/*.class\n /resources/locales.clj\n /mp-*\n ```\n\n### Web service changes\n\nIf you are working on an HTTP service, you will also need to make sure that\nwe properly handle the locale that the user requests via the\n`Accept-Language` header. The library contains the function\n`locale-negotiator` that you should use as a Ring middleware. It stores the\nnegotiated locale in the `*locale*` binding - ultimately, that's the locale\nthat the `tru` macro will use.\n\n### Testing and pseudo-localization\n\nFor testing, it is often useful to introduce translations that are\nmaintained separately from the generally used locales, and whose change is\ncontrolled by developers rather than translators. The `i18n` library uses\nthe file `resources/locales.clj`, which is generated and maintained by the\n`make` targets, to track for which locales translations are\navailable. Additional locales can be made available by putting one or more\n`locales.clj` files on the class path whose `:package` entry is the same as\nthe one in `resources/locales.clj` but that mentions additional\n`:locales`.\n\nThat makes it possible to introduce additional locales for testing by doing\nthe following:\n\n1. Create a file `test/locales.clj` by copying `resources/locales.clj` and\n   edit the copy by changing the `:locales` entry to the languages that\n   should be used for testing\n1. For each of the additional locales, create a message catalog. It will\n   generally be easiest to base that message catalog on properties files\n   rather than on `.po` files. If you added the `eo` locale, you need to\n   create a file `test/\u003cpackage path\u003e/Messages_eo.properties`.  Note that\n   pluralization is not currently supported in properties files.\n1. Use those additional locales in your tests. The `test/` directory of\n   this library has an example of that in the `test-tru` test in\n   `core_test.clj`.\n\nThe macro `with-user-locale` can be used to change the locale under which a\ncertain test should run, for example, with\n\n```clojure\n(let [eo (string-as-locale \"eo\")]\n  (with-user-locale eo\n    (testing \"user-locale is Esperanto\"\n      (is (= eo (user-locale))))))\n```\n\n## Translator usage\nTranslators for Puppet, don't use this workflow.  In the Puppet workflow POs are generated in our translation tool, from an up to date POT.  We don't, as developers, update or commit POs. So this may only be relevant should a developer want to test or generate a test language.\n\n### Generate a test .po file\nPrior to generating a po file, make sure the POT is up to date by running `make i18n`.\nThis will put new msgids from the app, into the POT.\n\nTo create a `.po` file for the language eo:\n\n    make locales/eo.po\n\nNote this will just take the contents of the current POT and write the PO from it.\nSubsequent runs will not keep that file up to date.\n\n### Update a test .po file\nPrior to updating a po file, make sure the POT is up to date by running `make i18n`.\nThis will put new msgids from the app, into the POT.\n\nTo update the po:\n    msgmerge -U locales/eo.po locales/\u003cproject-name\u003e.pot\n\nThis uses the contents of the current POT to update msgids in the target po (eo.po).\n\n## Release usage\n\nWhen it comes time to make a release, or if you want to use your code in a\ndifferent locale before then, you need to generate Java `ResourceBundle`\nclasses that contain the localized messages. This is done by running `make\nmsgfmt` on your project.\n\n# Hacking\n\nThe code is set up as an ordinary leiningen project, with the one exception\nthat you need to run `make` before running `lein test` or `lein run`, as\nthere are messages that need to be turned into a message bundle.\n\n# Maintenance\n\nMaintainers: David Lutterkort \u003clutter@puppetlabs.com\u003e and Libby Molina \u003clibby@puppetlabs.com\u003e\nTickets: File bug tickets at https://tickets.puppetlabs.com/browse/INTL, and add the `clj` component to the ticket.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpuppetlabs%2Fclj-i18n","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpuppetlabs%2Fclj-i18n","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpuppetlabs%2Fclj-i18n/lists"}