/i18n-tasks

Manage translation and localization with static analysis, for Ruby i18n

Primary LanguageRubyMIT LicenseMIT

i18n-tasks helps you find and manage missing and unused translations.

This gem analyses code statically for key usages, such as I18n.t('some.key'), in order to:

  • Report keys that are missing or unused.
  • Pre-fill missing keys, optionally from Google Translate.
  • Remove unused keys.

Thus addressing the two main problems of i18n gem design:

  • Missing keys only blow up at runtime.
  • Keys no longer in use may accumulate and introduce overhead, without you knowing it.

Installation

i18n-tasks can be used with any project using the ruby i18n gem (default in Rails).

Add it to the Gemfile:

gem 'i18n-tasks', '~> 0.7.13'

Copy default configuration file (optional):

$ cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/

Copy rspec test to test for missing and unused translations as part of the suite (optional):

$ cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/

Usage

Run i18n-tasks to get the list of all the tasks with short descriptions.

Check health

i18n-tasks health checks if any keys are missing or not used:

$ i18n-tasks health

Add missing keys

Add missing keys with placeholders (base value or humanized key):

$ i18n-tasks add-missing

This and other tasks accept arguments:

$ i18n-tasks add-missing -v 'TRME %{value}' fr

Pass --help for more information:

$ i18n-tasks add-missing --help
Usage: i18n-tasks add-missing [options] [locale ...]
    -l, --locales  Comma-separated list of locale(s) to process. Default: all. Special: base.
    -f, --format   Output format: terminal-table, yaml, json, keys, inspect. Default: terminal-table.
    -v, --value    Value. Interpolates: %{value}, %{human_key}, %{value_or_human_key}. Default: %{value_or_human_key}.
    -h, --help     Display this help message.

Google Translate missing keys

Translate missing values with Google Translate (more below on the API key).

$ i18n-tasks translate-missing
# accepts from and locales options:
$ i18n-tasks translate-missing --from base es fr

Find usages

See where the keys are used with i18n-tasks find:

$ i18n-tasks find common.help
$ i18n-tasks find 'auth.*'
$ i18n-tasks find '{number,currency}.format.*'

Remove unused keys

$ i18n-tasks unused
$ i18n-tasks remove-unused

These tasks will infer dynamic keys such as t("category.\#{category.name}") by default. Pass -s or --strict to disable this feature.

Normalize data

Sort the keys:

$ i18n-tasks normalize

Sort the keys, and move them to the respective files as defined by config.write:

$ i18n-tasks normalize -p

Compose tasks

i18n-tasks also provides composable tasks for reading, writing and manipulating locale data.

For example, add-missing implemented with missing, tree-set-value and data-merge:

$ i18n-tasks missing -fyaml fr | i18n-tasks tree-set-value 'TRME %{value}' | i18n-tasks data-merge

Another example, remove-unused implemented with unused and data-remove:

$ i18n-tasks unused -fyaml | i18n-tasks data-remove

See the full list of tasks with i18n-tasks --help.

Features and limitations

Relative keys

i18n-tasks offers partial support for relative keys, such as t '.title'.

✔ Keys relative to the file path they are used in (see relative roots configuration) are supported.

✔ Keys relative to controller.action_name in Rails controllers are supported. The closest def name is used.

Plural keys

✔ Plural keys, such as key.{one,many,other,...} are fully supported.

t() keyword arguments

scope keyword argument is supported, but only when it is the first argument.

default and other arguments are not supported.

Parsing keyword arguments correctly with Regexp is difficult. This can be improved with an s-expression parser.

Dynamic keys

By default, unused report will detect some dynamic keys and not report them, e.g.:

t 'category.' + category.key      # all 'category.:' keys considered used (: denotes one key segment)
t "category.#{category.key}.name" # all 'category.:.name' keys considered used

This will not be on by default in future versions, in favour of encouraging explicit i18n-tasks-use hints. For now, you can disable dynamic key inference by passing -s or --strict to unused tasks.

Configuration

Configuration is read from config/i18n-tasks.yml or config/i18n-tasks.yml.erb. Inspect configuration with i18n-tasks config.

Install the default config file with:

$ cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/

Settings are compatible with Rails by default.

Locales

By default, base_locale is set to en and locales are inferred from the paths to data files. You can override these in the config:

# config/i18n-tasks.yml
base_locale: en
locales: [es, fr] # This includes base_locale by default

internal_locale controls the language i18n-tasks reports in. Locales available are en and ru (pull request to add more!).

internal_locale: en

Storage

The default data adapter supports YAML and JSON files.

# config/i18n-tasks.yml
data:
  # configure YAML / JSON serializer options
  # passed directly to load / dump / parse / serialize.
  yaml:
    write:
      # do not wrap lines at 80 characters (override default)
      line_width: -1

Multiple locale files

Use data options to work with locale data spread over multiple files.

data.read accepts a list of file globs to read from per-locale:

# config/i18n-tasks.yml
data:
  read:
    # read from namespaced files, e.g. simple_form.en.yml
    - 'config/locales/*.%{locale}.yml'
    # read from a gem (config is parsed with ERB first, then YAML)
    - "<%= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
    # default
    - 'config/locales/%{locale}.yml'

Key pattern syntax

syntax description
* matches everything
: matches a single key
{a, b.c} match any in set, can use : and *, match is captured

For writing to locale files i18n-tasks provides 2 options.

Pattern router

Pattern router organizes keys based on a list of key patterns, as in the example below:

data:
  router: pattern_router
  # a list of {key pattern => file} routes, matched top to bottom
  write:
    # write models.* and views.* keys to the respective files
    - ['{models,views}.*', 'config/locales/\1.%{locale}.yml']
    # or, write every top-level key namespace to its own file
    - ['{:}.*', 'config/locales/\1.%{locale}.yml']
    # default, sugar for ['*', path]
    - 'config/locales/%{locale}.yml'
Conservative router

Conservative router keeps the keys where they are found, or infers the path from base locale. If the key is completely new, conservative router will fall back to pattern router behaviour. Conservative router is the default router.

data:
  router: conservative_router
  write:
    - ['devise.*', 'config/locales/devise.%{locale}.yml']
    - 'config/locales/%{locale}.yml'

Custom adapters

If you store data somewhere but in the filesystem, e.g. in the database or mongodb, you can implement a custom adapter. Implement a handful of methods, then set data.adapter to the class name; the rest of the data config is passed to the initializer.

# config/i18n-tasks.yml
data:
  # file_system is the default adapter, you can provide a custom class name here:
  adapter: file_system

Usage search

Configure usage search in config/i18n-tasks.yml:

# config/i18n-tasks.yml
# i18n usage search in source
search:
  # search these directories (relative to your Rails.root directory, default: 'app/')
  paths:
    - 'app/'
    - 'vendor/'
  # paths for relative key resolution:
  relative_roots:
    # default:
    - app/views
    # add a custom one:
    - app/views-mobile
  # include only files matching this glob pattern (default: blank = include all files)
  include:
    - '*.rb'
    - '*.html.*'
    - '*.text.*'
  # explicitly exclude files (default: exclude common binary files)
  exclude:
    - '*.js'
  # you can override the default key regex pattern:
  pattern: "\\bt[( ]\\s*(:?\".+?\"|:?'.+?'|:\\w+)"
  # comments are ignored by default
  ignore_lines:
    - "^\\s*[#/](?!\\si18n-tasks-use)"

It is also possible to use a custom key usage scanner by setting search.scanner to a class name. See this basic pattern scanner for reference.

Fine-tuning

Add hints to static analysis with magic comment hints (lines starting with (#|/) i18n-tasks-use by default):

# i18n-tasks-use t('activerecord.models.user') # let i18n-tasks know the key is used
User.model_name.human

You can also explicitly ignore keys appearing in locale files:

# config/i18n-tasks.yml
# do not report these keys as unused
ignore_unused:
  - category.*.db_name

# do not report these keys as missing (both on blank value and no key)
ignore_missing:
  - devise.errors.unauthorized # ignore this key
  - pagination.views.*         # ignore the whole pattern
  # E.g to ignore all Rails number / currency keys:
  - 'number.{format, percentage.format, precision.format, human.format, currency.format}.{strip_insignificant_zeros,significant,delimiter}'
  - 'time.{pm,am}'

# do not report these keys when they have the same value as the base locale version
ignore_eq_base:
  all:
    - common.ok
  es,fr:
    - common.brand

# do not report these keys ever
ignore:
  - kaminari.*

Google Translate

i18n-tasks translate-missing requires a Google Translate API key, get it at Google API Console.

Where this key is depends on your Google API console:

  • Old console: API Access -> Simple API Access -> Key for server apps.
  • New console: Project -> APIS & AUTH -> Credentials -> Public API access -> Key for server applications.

In both cases, you may need to create the key if it doesn't exist.

Put the key in GOOGLE_TRANSLATE_API_KEY environment variable or in the config file.

# config/i18n-tasks.yml
translation:
  api_key: <Google Translate API key>

Interactive Console

i18n-tasks irb starts an IRB session in i18n-tasks context. Type guide for more information.

XLSX

Export missing and unused data to XLSX:

$ i18n-tasks xlsx-report

HTML

While i18n-tasks does not provide an HTML version of the report, you can add one like this.

Add New Tasks

Tasks that come with the gem are defined in lib/i18n/tasks/command/commands.

Add a custom task like the ones defined by the gem:

# my_commands.rb
module MyCommands
  include ::I18n::Tasks::Command::Collection
  cmd :my_task, desc: 'my custom task'
  def my_task(opts = {})
  end
end
# config/i18n-tasks.yml
<%
  require './my_commands'
  I18n::Tasks::Commands.send :include, MyCommands
%>

Run with:

$ i18n-tasks my-task