{"id":18544463,"url":"https://github.com/instructure/i18nliner","last_synced_at":"2025-10-29T00:46:27.149Z","repository":{"id":12226142,"uuid":"14835403","full_name":"instructure/i18nliner","owner":"instructure","description":"I18n made simple","archived":false,"fork":false,"pushed_at":"2023-06-05T14:03:35.000Z","size":135,"stargazers_count":24,"open_issues_count":5,"forks_count":9,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-10-29T07:41:48.315Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/instructure.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2013-12-01T07:51:46.000Z","updated_at":"2024-05-27T18:44:27.000Z","dependencies_parsed_at":"2023-07-14T11:15:44.364Z","dependency_job_id":null,"html_url":"https://github.com/instructure/i18nliner","commit_stats":{"total_commits":125,"total_committers":7,"mean_commits":"17.857142857142858","dds":"0.17600000000000005","last_synced_commit":"9770180c0a4083f0153b8de76de5ac23f87b0ce3"},"previous_names":["jenseng/i18nliner"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Fi18nliner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Fi18nliner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Fi18nliner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Fi18nliner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/instructure","download_url":"https://codeload.github.com/instructure/i18nliner/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223407796,"owners_count":17140563,"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-11-06T20:16:36.903Z","updated_at":"2025-10-29T00:46:22.100Z","avatar_url":"https://github.com/instructure.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# I18nliner\n\n[\u003cimg src=\"https://secure.travis-ci.org/jenseng/i18nliner.png\" /\u003e](http://travis-ci.org/jenseng/i18nliner)\n\nI18nliner is I18n made simple.\n\nNo .yml files. Inline defaults. Optional keys. Inferred interpolation values.\nWrappers and blocks, so your templates look template-y and your translations\nstay HTML-free.\n\n## TL;DR\n\nI18nliner lets you do stuff like this:\n\n```ruby\nt \"Ohai %{@user.name}, my default translation is right here in the code. \" \\\n  \"Inferred keys and placeholder values, oh my!\"\n```\n\nand even this:\n\n```erb\n\u003c%= t do %\u003e\n  Hey \u003c%= amigo %\u003e!\n  Although I am \u003c%= link_to \"linking to something\", random_path %\u003e and\n  have some \u003cstrong\u003ebold text\u003c/strong\u003e, the translators will see\n  \u003cstrong\u003e\u003cem\u003eabsolutely no markup\u003c/em\u003e\u003c/strong\u003e and will only have a\n  single string to translate :o\n\u003c% end %\u003e\n```\n\n## Installation\n\nAdd the following to your Gemfile:\n\n```ruby\ngem 'i18nliner'\n```\n\n## Features\n\n### No more en.yml\n\nInstead of maintaining .yml files and doing stuff like this:\n\n```ruby\nI18n.t :account_page_title\n```\n\nForget the .yml and just do:\n\n```ruby\nI18n.t :account_page_title, \"My Account\"\n```\n\nRegular I18n options follow the (optional) default translation, so you can do\nthe usual stuff (placeholders, etc.).\n\n#### Okay, but don't the translators need en.yml?\n\nSure, but *you* don't need to write it. Just run:\n\n```bash\nrake i18nliner:dump\n```\n\nThis extracts all default translations from your codebase, merges them with any\nother ones (from rails or pre-existing .yml files), and outputs them to\n`config/locales/generated/en.yml` (or rather, `\"#{I18n.default_locale}.yml\"`).\n\n### It's okay to lose your keys\n\nWhy waste time coming up with keys that are less descriptive than the default\ntranslation? I18nliner makes keys optional, so you can just do this:\n\n```ruby\nI18n.t \"My Account\"\n```\n\nI18nliner will create a unique key based on the translation (e.g.\n`:my_account`), so you don't have to. See `I18nliner.inferred_key_format` for\nmore information.\n\nThis can actually be a **good thing**, because when the `en` changes, the key\nchanges, which means you know you need to get it retranslated (instead of\nletting a now-inaccurate translation hang out indefinitely). Whether you want\nto show \"[ missing translation ]\" or the `en` value in the meantime is up to\nyou.\n\n### Inferred Interpolation Values\n\nInterpolation values may be inferred by I18nliner if not provided. So long as\nit's an instance variable or method (or chain), you don't need to specify its\nvalue. So this:\n\n```erb\n\u003cp\u003e\n  \u003c%= t \"Hello, %{user}. This request was a %{request_method}.\",\n        user: @user.name,\n        request_method: request.method\n  %\u003e\n\u003c/p\u003e\n```\n\nCan just be this:\n\n```erb\n\u003cp\u003e\n  \u003c%= t \"Hello, %{@user.name}. This request was a %{request.method}.\" %\u003e\n\u003c/p\u003e\n```\n\nNote that local variables cannot be inferred.\n\n### Wrappers and Blocks\n\n#### The Problem\n\nSuppose you have something like this in your ERB:\n\n```erb\n\u003cp\u003e\n  You can \u003c%= link_to \"lead\", new_discussion_path %\u003e a new discussion or\n  \u003c%= link_to \"join\", discussion_search_path %\u003e an existing one.\n\u003c/p\u003e\n```\n\nYou might try something like this:\n\n```erb\n\u003cp\u003e\n  \u003c%= t(\"You can %{lead} a new discussion or %{join} an existing one.\",\n        lead: link_to(t(\"lead\"), new_discussion_path),\n        join: link_to(t(\"join\"), discussion_search_path)).html_safe\n  %\u003e\n\u003c/p\u003e\n```\n\nThis is not great, because:\n\n1. There are three strings to translate.\n2. When translating the verbs, the translator has no context for where it's\n   being used... Is \"lead\" a verb or a noun?\n3. Translators have their hands somewhat tied as far as what is inside the\n   links and what is not.\n\nSo you might try this instead:\n\n```erb\n\u003cp\u003e\n  \u003c%= t :discussion_html,\n        \"You can \u003ca href=\"%{lead_url}\"\u003elead\u003c/a\u003e a new discussion or \" \\\n        \"\u003ca href=\"%{join_url}\"\u003ejoin\u003c/a\u003e an existing one.\",\n        lead_url: new_discussion_path,\n        join_url: discussion_search_path\n  %\u003e\n\u003c/p\u003e\n```\n\nThis isn't much better, because now you have HTML in your translations. If you\nwant to add a class to the link, you have to go update all the translations.\nA translator could accidentally break your page (or worse, cross-site script\nit).\n\nSo what do you do?\n\n#### Wrappers\n\nI18nliner lets you specify wrappers, so you can keep HTML out the translations,\nwhile still just having a single string needing translation:\n\n```erb\n\u003cp\u003e\n  \u003c%= t \"You can *lead* a new discussion or **join** an existing one.\",\n        wrappers: [\n          link_to('\\1', new_discussion_path),\n          link_to('\\1', discussion_search_path)\n        ]\n  %\u003e\n\u003c/p\u003e\n```\n\nDefault delimiters are increasing numbers of asterisks, but you can specify\nany string as a delimiter by using a hash rather than an array.\n\n#### Blocks\n\nBut wait, there's more!\n\nPerhaps you want your templates to look like, well, templates. Try this:\n\n```erb\n\u003cp\u003e\n  \u003c%= t do %\u003e\n    Welcome to the internets, \u003c%= user.name %\u003e\n  \u003c% end %\u003e\n\u003c/p\u003e\n```\n\nOr even this:\n\n```erb\n\u003cp\u003e\n  \u003c%= t do %\u003e\n    \u003cb\u003eOhai \u003c%= user.name %\u003e,\u003c/b\u003e\n    you can \u003c%= link_to \"lead\", new_discussion_path %\u003e a new discussion or\n    \u003c%= link_to \"join\", discussion_search_path %\u003e an existing one.\n  \u003c% end %\u003e\n\u003c/p\u003e\n```\n\nIn case you're curious about the man behind the curtain, I18nliner adds an ERB\npre-processor that turns the second example into something like this right\nbefore it hits ERB:\n\n```erb\n\u003cp\u003e\n  \u003c%= t :some_unique_key,\n        \"*Ohai %{user_name}*, you can **lead** a new discussion or ***join*** an existing one.\",\n        user_name: user.name,\n        wrappers: [\n          '\u003cb\u003e\\1\u003c/b\u003e',\n          link_to('\\1', new_discussion_path),\n          link_to('\\1', discussion_search_path)\n        ]\n  %\u003e\n\u003c/p\u003e\n```\n\nIn other words, it will infer wrappers from your (balanced) markup and\n`link_to` calls, and will create placeholders for any\nother (inline) ERB expressions. ERB statements (e.g.\n`\u003c% if some_condition %\u003e...`) and block expressions (e.g.\n`\u003c%= form_for @person do %\u003e...`) are *not* supported within a block\ntranslation. The only exception to this rule is nested translation\ncalls, e.g. this is totally fine:\n\n```erb\n\u003c%= t do %\u003e\n  Be sure to\n  \u003ca href=\"/account/\" title=\"\u003c%= t do %\u003eAccount Settings\u003c% end %\u003e\"\u003e\n    set up your account\n  \u003c/a\u003e.\n\u003c% end %\u003e\n```\n\n#### HTML Safety\n\nI18nliner ensures translations, interpolated values, and wrappers all play\nnicely (and safely) when it comes to HTML escaping. If any translation,\ninterpolated value, or wrapper is HTML-safe, everything else will be HTML-\nescaped.\n\n### Inline Pluralization Support\n\nPluralization can be tricky, but [I18n gives you some flexibility](http://guides.rubyonrails.org/i18n.html#pluralization).\nI18nliner brings this inline with a default translation hash, e.g.\n\n```ruby\nt({one: \"There is one light!\", other: \"There are %{count} lights!\"},\n  count: picard.visible_lights.count)\n```\n\nNote that the :count interpolation value needs to be explicitly set when doing\npluralization.\n\nIf you just want to pluralize a single word, there's a shortcut:\n\n```ruby\nt \"person\", count: users.count\n```\n\nThis is equivalent to:\n\n```ruby\nt({one: \"1 person\", other: \"%{count} people\"},\n  count: users.count)\n```\n\nI18nliner uses [`String#pluralize`](http://edgeguides.rubyonrails.org/active_support_core_extensions.html#pluralize)\nto determine the default one/other values,\nso if your `I18n.default_locale` is something other than English, you may need\nto [add some inflections](https://gist.github.com/838188).\n\n## Rake Tasks\n\n### i18nliner:check\n\nEnsures that there are no problems with your translate calls (e.g. missing\ninterpolation values, reusing a key for a different translation, etc.). **Go\nadd this to your Jenkins/Travis tasks.**\n\n### i18nliner:dump\n\nDoes an i18nliner:check, and then extracts all default translations from your\ncodebase, merges them with any other ones (from rails or pre-existing .yml\nfiles), and outputs them to `config/locales/generated/en.yml`.\n\n#### Dynamic Translations\n\nNote that check and dump commands require all translation keys and\ndefaults to be literals. This is because it reads your code, it doesn't\nrun it. If you know what you are doing and want to pass in a variable or\nother expression, you can use the `t!` (or `translate!`) method. It works\nthe same as `t` at runtime, but signals to the extractor that it shouldn't\ncomplain. You should only do this if you are sure that the specified\nkey/string is extracted elsewhere or already in your yml.\n\n#### .i18nignore and more\n\nBy default, the check and dump tasks will look for inline translations in any\n.rb or .erb files. You can tell it to always skip certain\nfiles/directories/patterns by creating a .i18nignore file. The syntax is the\nsame as [.gitignore](http://www.kernel.org/pub/software/scm/git/docs/gitignore.html),\nthough it supports\n[a few extra things](https://github.com/jenseng/globby#compatibility-notes).\n\nIf you only want to check a particular file/directory/pattern, you can set the\nenvironment variable `ONLY` when you run the command, e.g.\n\n```bash\nrake i18nliner:check ONLY=/app/**/user*\n```\n\n## Compatibility\n\nI18nliner is backwards compatible with I18n, so you can add it to an\nestablished (and already internationalized) Rails app. Your existing\ntranslation calls, keys and yml files will still just work without\nmodification.\n\nI18nliner requires at least Ruby 1.9.3 and Rails 3.\n\n## Related Projects\n\n* [i18nliner-js](https://github.com/jenseng/i18nliner-js)\n* [i18nliner-handlebars](https://github.com/fivetanley/i18nliner-handlebars)\n* [react-i18nliner](https://github.com/jenseng/react-i18nliner)\n\n## License\n\nCopyright (c) 2015 Jon Jensen, released under the MIT license\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finstructure%2Fi18nliner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finstructure%2Fi18nliner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finstructure%2Fi18nliner/lists"}