A Go CLI that fully replaces i18n-tasks for a Rails app and adds capabilities
i18n-tasks lacks: automatic hardcoded-string extraction from ERB, automatic
t() call injection, ERB fragment merging, Google Translate integration,
deduplication, and key refactoring.
It is the primary i18n toolchain for apps/dashboard. Do not use
i18n-tasks write commands (add-missing, remove-unused, normalize) — use
rori18n instead. i18n-tasks is kept only as a passive health-check tool
(bundle exec i18n-tasks health).
main.go entry point (delegates to cmd.Execute())
cmd/ one file per command
internal/
locale/ YAML read/write, key dedup, value linting
source/ ERB/Ruby scanning, t() injection, fragment merging
translate/ Google Cloud Translation API client
See docs/core/ARCHITECTURE.md for full package internals.
Write safety: Every write command uses setYAMLPath() with a nil
overwrite callback — it skips any key that already has a non-empty value.
translate is the only exception: it overwrites values that match
IsPlaceholder() (empty string or ^TODO:|^FIXME: regex). Real translations
written by a human are never overwritten by any command.
Key routing: Keys are routed to YAML files by their top-level namespace.
dashboard.foo.bar → config/locales/en/dashboard.en.yml. Unknown namespaces →
shared.en.yml. This is determined in locale.UpsertTopicFile().
All-lang by default: refactor-key has --all-lang (default true) — it
renames the key in every language directory. discoverLangs() reads
subdirectories of config/locales/ to find them.
Mailer keys: Mailer views use relative keys (t '.subject') that static
scanners can't resolve. *_mailer.* is in ignore_unused in i18n-tasks.yml
for this reason. rori18n's lint and audit commands have a parallel
limitation — mailer relative keys are treated as used by matching against the
view-path resolved key in source.ResolveRelativeKey().
Dynamic keys: ERB that interpolates full key segments
(t("#{i18n_scope}.heading")) cannot be statically resolved. These are excluded
from prune via --pattern flag or pre-emptively excluded in i18n-tasks.yml
with ignore_unused patterns.
YAML 1.1 boolean key bug: Keys named yes, no, true, false must be
quoted in YAML ("yes":) or Ruby's Psych parser treats them as booleans and
lookups return nil. rori18n does not currently detect this — it's caught at
runtime or by manual YAML review.
- Create
cmd/<name>.gofollowing the pattern in any existing cmd file. - Register with
rootCmd.AddCommand(...)in aninit()function. - Use
locale.Scan(root, lang)to read entries,locale.UpsertTopicFile()to write. - Add
--dry-runand--rootflags to every write command. - No tests required in
cmd/— test the logic ininternal/packages instead.
# build once
go build -o rori18n .
# or run directly
go run . <command> [flags]
# tests
go test ./...google.json in this directory is the service account key. Never commit it.
Pass via environment variable:
GOOGLE_APPLICATION_CREDENTIALS=google.json go run . translate \
--root <rails-app-root> --from en --to esAdd a missing key across all locales:
go run . add-key --root <rails-app-root> \
--key shared.buttons.save --value "Save changes"
# then translate it:
GOOGLE_APPLICATION_CREDENTIALS=google.json go run . translate \
--root <rails-app-root> --to es,frRename a key:
go run . refactor-key --root <rails-app-root> \
--old shared.common.copy_btn --new shared.buttons.copy --dry-run
go run . refactor-key --root <rails-app-root> \
--old shared.common.copy_btn --new shared.buttons.copyExtract hardcoded strings from a new view file:
go run . report --root <rails-app-root> # see what's there
go run . generate --root <rails-app-root> --fix --dry-run
go run . generate --root <rails-app-root> --fix
GOOGLE_APPLICATION_CREDENTIALS=google.json go run . translate \
--root <rails-app-root> --to es,frAfter a big refactor (check for dead keys):
go run . prune --root <rails-app-root> --dry-run
go run . prune --root <rails-app-root>