Module: RailsErrorDashboard::I18nStore
- Defined in:
- lib/rails_error_dashboard/i18n_store.rb
Overview
RED's own translation store, deliberately isolated from the host app's I18n.
WHY A PRIVATE BACKEND INSTEAD OF THE USUAL ENGINE LOAD PATH
The conventional way to translate a Rails engine is to append config/locales to I18n.load_path and namespace the keys. That shares the host's backend, and sharing it hands the host three ways to break the dashboard:
1. config.i18n.raise_on_missing_translations = true turns any key we
forgot into a 500 — on the error dashboard, the one page that has to
work when everything else is broken.
2. enforce_available_locales with a short available_locales list raises
I18n::InvalidLocale as soon as RED asks for its own locale.
3. A custom exception_handler can raise on anything it likes.
RED's locale is also deliberately independent of the host's — that is the whole point of issue #148. Sharing a backend would re-create the coupling that bug was about.
The trade-off: hosts cannot override RED's strings with their own locale files. That is the right default for a self-hosted ops tool, and it can be relaxed later without breaking anything.
NOTHING IN HERE MAY RAISE. Every public method is total: it returns a String for any input, including garbage. See #translate.
Constant Summary collapse
- DEFAULT_LOCALE =
"en".freeze
- ENDONYMS =
The language's own name for itself. See .locale_options for why these are constants rather than translation keys.
{ "en" => "English", "de" => "Deutsch", "fr" => "Français", "es" => "Español", "pt-BR" => "Português (Brasil)", "ja" => "日本語", "ru" => "Русский", "uk" => "Українська", "pl" => "Polski", "zh-CN" => "简体中文", "it" => "Italiano" }.freeze
Class Method Summary collapse
-
.available?(value) ⇒ Boolean
Compares case-insensitively rather than against a downcased copy — "pt-BR" is a real locale filename and downcasing it would report a locale we ship as unavailable.
-
.available_locales ⇒ Array<Symbol>
Locales RED ships, derived from the files actually present.
-
.backend ⇒ Object
Double-checked locking.
-
.locale_options ⇒ Array<Array(String, String)>
Each shipped locale paired with its ENDONYM — the language's own name for itself (Deutsch, not German).
-
.reset! ⇒ Object
Test seam.
-
.resolve(value) ⇒ String
Resolve an arbitrary value to a locale RED can actually serve.
-
.subtree(key, locale: DEFAULT_LOCALE) ⇒ Hash
Fetch a whole branch of the dictionary as a Hash, for callers that need the tree rather than one leaf — the JS payload is the only one today.
-
.translate(key, locale: DEFAULT_LOCALE, **options) ⇒ String
(also: t)
Translate
keyinlocale, falling back to English, then to a readable last resort derived from the key itself.
Class Method Details
.available?(value) ⇒ Boolean
Compares case-insensitively rather than against a downcased copy — "pt-BR" is a real locale filename and downcasing it would report a locale we ship as unavailable.
157 158 159 160 161 162 163 164 |
# File 'lib/rails_error_dashboard/i18n_store.rb', line 157 def available?(value) candidate = value.to_s.strip return false if candidate.empty? available_locales.any? { |locale| locale.to_s.casecmp?(candidate) } rescue StandardError false end |
.available_locales ⇒ Array<Symbol>
Locales RED ships, derived from the files actually present.
113 114 115 |
# File 'lib/rails_error_dashboard/i18n_store.rb', line 113 def available_locales @available_locales ||= locale_files.map { |path| File.basename(path, ".yml").to_sym }.sort end |
.backend ⇒ Object
Double-checked locking. The fast path reads a fully-built backend; the slow path builds it under the mutex. build_backend assigns only after load_translations returns, so no thread can observe a half-loaded backend through @backend.
170 171 172 173 174 175 176 177 |
# File 'lib/rails_error_dashboard/i18n_store.rb', line 170 def backend cached = @backend return cached if cached load_mutex.synchronize do @backend ||= build_backend end end |
.locale_options ⇒ Array<Array(String, String)>
Each shipped locale paired with its ENDONYM — the language's own name for itself (Deutsch, not German).
Deliberately NOT translation keys. An endonym is a property of the language, not of the locale you are viewing from: a picker that renders "German" when viewed from English and "Deutsch" when viewed from German is unusable precisely when you need it, which is when you are stuck in a language you cannot read. Every entry reads the same in every locale.
A locale with a file but no entry here falls back to its own tag ("xh"), which is honest and still selectable, rather than being hidden.
131 132 133 134 135 |
# File 'lib/rails_error_dashboard/i18n_store.rb', line 131 def available_locales.map { |locale| [ locale.to_s, ENDONYMS.fetch(locale.to_s, locale.to_s) ] } rescue StandardError [ [ DEFAULT_LOCALE, ENDONYMS.fetch(DEFAULT_LOCALE) ] ] end |
.reset! ⇒ Object
Test seam. Clears memoized state so specs can reload from disk.
180 181 182 183 184 185 |
# File 'lib/rails_error_dashboard/i18n_store.rb', line 180 def reset! load_mutex.synchronize do @backend = nil @available_locales = nil end end |
.resolve(value) ⇒ String
Resolve an arbitrary value to a locale RED can actually serve. Matches case-insensitively ("EN" -> :en) because a wrong-cased tag that passes a format check but misses the dictionary is a mid-render failure.
142 143 144 145 146 147 148 149 150 |
# File 'lib/rails_error_dashboard/i18n_store.rb', line 142 def resolve(value) candidate = value.to_s.strip return DEFAULT_LOCALE if candidate.empty? match = available_locales.find { |locale| locale.to_s.casecmp?(candidate) } match ? match.to_s : DEFAULT_LOCALE rescue StandardError DEFAULT_LOCALE end |
.subtree(key, locale: DEFAULT_LOCALE) ⇒ Hash
Fetch a whole branch of the dictionary as a Hash, for callers that need the tree rather than one leaf — the JS payload is the only one today.
#translate deliberately treats a Hash result as a miss: a key resolving to a subtree instead of a leaf is a caller bug when you asked for text. Here it is the point, so this is a separate method rather than a flag on #translate.
Falls back to English as a whole branch, not key by key. A partially translated locale returning a half-English tree would be harder to debug than one that is cleanly English until it is finished.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/rails_error_dashboard/i18n_store.rb', line 95 def subtree(key, locale: DEFAULT_LOCALE) return {} if key.nil? || key.to_s.empty? resolved = lookup_subtree(key, locale) return resolved unless resolved.equal?(MISSING) unless locale.to_s == DEFAULT_LOCALE fallback = lookup_subtree(key, DEFAULT_LOCALE) return fallback unless fallback.equal?(MISSING) end {} rescue StandardError {} end |
.translate(key, locale: DEFAULT_LOCALE, **options) ⇒ String Also known as: t
Translate key in locale, falling back to English, then to a
readable last resort derived from the key itself.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/rails_error_dashboard/i18n_store.rb', line 62 def translate(key, locale: DEFAULT_LOCALE, **) return "" if key.nil? || key.to_s.empty? resolved = lookup(key, locale, ) return resolved unless resolved.equal?(MISSING) unless locale.to_s == DEFAULT_LOCALE fallback = lookup(key, DEFAULT_LOCALE, ) return fallback unless fallback.equal?(MISSING) end humanized_key(key) rescue StandardError # Truly last resort. A translation lookup must never be the reason a # dashboard page fails to render. humanized_key(key) end |