Use Rails 6 I18n translations for custom/user-specific naming schemas - ruby-on-rails

What I would like to do is utilize the I18n localization structure to handle organziation-specific naming schemas, independently of the users actual local. Something like:
Default Naming Schema
show.en.yml
en:
show:
title: Component
Organization A Nameing Schema
show.org_a.yml
org_a:
show:
title: Widget
Organization B Nameing Schema
show.org_b.yml
org_b:
show:
title: Sprocket
I've tried to just set a custom local using I18n.locale = organziation.slug.to_sym || I18n.default_locale (where slug would be org_a, org_b, etc.), but that just yields a :org_a is not a valid locale error. Unfortunately, searching for possible solutions has not been all that fruitful.
Is there a way to add custom locals, or should I be approaching this problem a different way (using Rails 6)?

This works in Rails 4.2 (since that is what I have easy access to right now) to allow for custom locales but it should work for Rails 6 as well (or something similar to this):
I18n.available_locales = I18n.available_locales + [:org_a, :org_b]
I18n.locale = organziation.slug.to_sym || I18n.default_locale
Maybe append to the available locales early on in a config file, or similar.

Related

Rails 5 - Problem with Umlaute and parameterize method

I work on Rails 5.2.3 and Ruby 2.5.1. At some point, I found an issue when I expected my array-of-string constant to contain some string but it didn't. Turned out the problem was related to German Umlaute characters (öäü).
So I have the constant defined like the following:
# coding: utf-8
# frozen_string_literal: true
class MyClass
module MyModule
MY_CONSTANT = [
'Breite in mm',
'Höhe in mm',
'Länge in mm'
].map(&:parameterize).freeze
end
end
I expect the constant to look like ["breite-in-mm", "hoehe-in-mm", "laenge-in-mm"]
But instead it's stored as ["breite-in-mm", "hohe-in-mm", "lange-in-mm"]. You see, "ö" has been converted to "o" instead of "oe". Same for "ä". Now it's "a", not "ae".
It works this way on production, in RSpec tests and even when I start Rails console and call this constant. But when I define a new constant from Rails console using the very same code, the strings are being successfully converted to what I expect, i.e. ["breite-in-mm", "hoehe-in-mm", "laenge-in-mm"]
I could easily get rid of this parameterize method and just type in the strings as I need them. Maybe I will have to do that. But I'm really curious about why all this is happening and couldn't find an answer by myself.
So thank you in advance for any ideas.
The parameterize method in Rails (through its use of ActiveSupport::Inflector#transliterate) is in locale aware. It thus uses locale-depending rules to transliterate characters such as Umlauts to ASCII characters.
When your app handles a request (or at least once after booting), you are usually setting a I18n locale, e.g. with I18n.locale = :de for a single request or with I18n.default_locale = :de for your whole app. After that, Rails (resp. the i18n gem) used this locale by default for its transliteration rules.
When initially setting your constant, this default locale was likely not yet set. The i18n gem is thus not aware of the German transliteration rules and uses only the basic Unicode normalization rules.
As a workaround, you can either pass the desired locale to use to the parameterize method as
MY_CONSTANT = [
'Breite in mm',
'Höhe in mm',
'Länge in mm'
].map { |const| const.parameterize(locale: :de).freeze }.freeze
or you can alternatively set the default i18n locale earlier than when your code is executed (e.g. in a file in config/initializers, depending on where exactly you initialize your constant):
I18n.default_locale = :de
Thanks, Holger Just for your great answer. It seems to be correct except it only works for Rails 6.0.0. So I'm going to post the one for Rails 5.2.3 which I'm using on my project.
Unfortunately in Rails 5 parameterize method does not accept the locale argument yet. This will be possible only in Rails 6.
But still, as mentioned in Holger Just's answer, parameterize method relies on transliterate method which does actually use current locale and converts strings according to it.
See Rails 5.2.3 docs and sources for those methods:
https://api.rubyonrails.org/v5.2.3/classes/ActiveSupport/Inflector.html#method-i-parameterize
https://api.rubyonrails.org/v5.2.3/classes/ActiveSupport/Inflector.html#method-i-transliterate
So I cannot pass the locale to parameterize method directly. Then I should set the locale before my constant is defined.
Setting I18n.default_locale = :de inside application.rb file did not help. I already had that and the strings have been transliterated regardless.
What eventually helped was setting I18n.locale = :de manually. Thanks to this, I got my strings parameterized correctly without any changes to MyConstant definition.

Different translations per environment

I'd like certain translations to have different values depending on the environment.
Rails.env.test? && I18n.t('foo') # => 'A value'
Rails.env.development? && I18n.t('foo') # => 'A different value'
For accomplishing this I can imagine a few approaches:
Ability to embed ERB in en.yml
Ability to create per-environment locale files, e.g. en.test.yml
Ability to directly set an entry: I18n.set('foo', 'bar') if Rails.env.test?
However I couldn't find any of these solutions ready to use.
Is there one?
I think that You need something like this:
en.production.yml:
en:
production:
foo: "Foo"
bar: "Bar"
en.development.yml:
en:
development:
foo: "foo"
bar: "bar"
en.test.yml:
en:
test:
foo: "-foo-"
bar: "-bar-"
and using is views and controllers:
I18n.t("#{Rails.env}.foo")
I18n.t("#{Rails.env}.bar")
Update:
If You just need to substitute variables to translation, use interpolation:
en.yml:
en:
foo: "Foo %{variable}"
bar: "%{var} Bar"
and using:
I18n.t('foo', :variable => 'Bar')
I18n.t('bar', :var => 'Foo')
Late to the party. I've created a gem that does that by using environment variables. i18n-env-var-lookup . You can use it in combination with an environment variable loader such as dotenv-rails
My specific use case is a bit unique though. In our org, we have an existing rails app that we wanted to "white-label" for another project by another team in the org. We wanted to share the same source code repo for both projects while only change the site name. Given that there are already a few thousands entries in the locale files and close to a hundred of them contain the site name, it would be a bad practice to update all the templates to pass the site name as a variable. Other suggestions that we found in related questions are also no good in our case. We want to keep the locale files exactly the same for both project so that we can avoid messing with the source code and manage the locale files easily with 3rd-party localization service.
Therefore we turned to implementing the gem, and it solves our exact problem.

Rails 4.1 locale and 'version' based on url (.com, .cz, .de, etc)

Hope somebody can point me at the right direction with this...
Basically, i have the locales setup and it works fine. However, i need to depending on how the user gets to the site (example_company.com, example_company.cz or example_company.de..) have slightly different content(views and layout).
I've managed to boil it down to a constant or env variable that if i was to run multiple instances of the site(1 for each country), i could set on the server so that i get the behaviour i need with 1 code base.
My question is, how are people dealing with this in general? is there any way i can serve all countries on the same instance and set some flag based on .com or .cz or whatever, that dictates which 'version' they get without effecting the url itself?
I already have the locales in the url and would prefer not to mix the two as i will have to support multiple languages for each version. For example, french and czech would still support english.. But if i go to the french one, i will only show 2 locales (french and english)...
Hope i managed to explain properly.. if not let me know and i will try again.
If you use Rails' built-in i18n support, you can easily select locales by TLD.
From the official Rails i18n guide:
One option you have is to set the locale from the domain name where your application runs. For example, we want www.example.com to load the English (or default) locale, and www.example.es to load the Spanish locale. Thus the top-level domain name is used for locale setting. This has several advantages:
The locale is an obvious part of the URL.
People intuitively grasp in which language the content will be displayed.
It is very trivial to implement in Rails.
Search engines seem to like that content in different languages lives at different, inter-linked domains.
You can implement it like this in your ApplicationController:
before_action :set_locale
def set_locale
I18n.locale = extract_locale_from_tld || I18n.default_locale
end
# Get locale from top-level domain or return nil if such locale is not available
# You have to put something like:
# 127.0.0.1 application.com
# 127.0.0.1 application.it
# 127.0.0.1 application.pl
# in your /etc/hosts file to try this out locally
def extract_locale_from_tld
parsed_locale = request.host.split('.').last
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
Be sure to read the i18n guide in full. It covers how to use the built-in i18n support. A big advantage is you don't need separate views for each locale.

rails: how can I override a locale based on subdomain?

I have internationalized and localized my application using the standard rails mechanisms.
Everything is stored in en, fr, de.yml files.
My application is multi-tenant, based on the subdomain.
I would like to allow my users to override certain translations in the application (e.g. to change "Employee" to "Associate" because it matches their own terminology).
I've tried to change the load path of my yml files on a per-request basis, but to no avail.
Any idea how I could, for each request, look up first in my user-specific yaml file, and fall back to the default yaml file if the translation was not overriden?
Assuming you store the subdomain in an instance variable from a controller filter, you could override the translation helper to do a lookup with a subdomain-specific scope first, then fallback to the specified or default scope. Something like this:
module ApplicationHelper
def t(key, original_options = {})
options = original_options.dup
site_translation_scope = ['subdomain_overrides', #subdomain.name]
scope =
case options[:scope]
when nil
site_translation_scope
when Array
options[:scope] = site_translation_scope + options[:scope]
when String
[site_translation_scope, options[:scope]].join(".")
end
translate(key, options.merge(:scope => scope, :raise => true))
rescue I18n::MissingTranslationData
translate(key, original_options)
end
end
Then you add your subdomain-specific overrides likes this:
en:
customer: Customer
subdomain_overrides:
subdomain_1:
customer: Buyer
If you want to allow tenants to use specific language but fallback to the default, I have written a micro library that will get the job done:
https://github.com/ElMassimo/i18n_multitenant
It takes care of configuring I18n to fallback to the base locale, allowing you to use tenant-specific translations if available. It's designed to work with the default backend of static .yml files, but it should also work with other I18n backends.
I've recently created I18n_global_scope gem that does exactly what you are describing, please checkout the source code https://github.com/mobilityhouse/i18n_global_scope and let me know your feedback.

Internationalization Best Practices / Rails App

I am new to ruby & rails and have started building an application.
My goal is to build this in a way I can easily translate the contents of the rails app and display the website contents in a locale preferred by registered user.
Appreciate any inputs on some of the best practices or references to any documentation to read, to build a web application that can be easily translated?
Thanks,
Krish.
Check out the Rails Internationalization (I18n) API. It does everything you've described.
Also check out Globalize3, it became a standard for model translations. Very useful.
You can use ready_for_i18n plugin that convert your erb to desired form.It saves some time.
You'd definetely watch this talk: http://www.youtube.com/watch?v=CTu4iHWGDyE
Here are some tips or best practices I noted while working through Internationalization of a Rails app. (It's possible that some of them are now outdated).
Before I get into a list here's some clarification between localization and translation that was helpful to me:
Definitions:
App localization and model translations are separate concerns. Figure out which one of those (or both) it is, that you need.
The way I use them here:
App localization: Localization your app to a locale.
Model translation: Translation your model/data into a language.
Example: Give me the french translation (model translation) of the resource in my Spanish site (app localization)
I use Globalize for model translations.
Helpful Tips/ Best Practices:
Some of the following are clearly just helpful tips while working in the Context of Rails + Globalize, some of them might be more than that... possibly best practices.
I18n.locale refers to and sets the locale of the app. When using Globalize, Globalize.locale refers to and sets the locale for model/data translations.
Set both I18n.locale and Globalize.locale on every request. Since these variables are set in Thread, it will avoid some hard-to-replicate bugs.
Set Globalize.locale to I18n.locale right after setting I18n.locale. This allows for model translations to default to the locale of the app. (On my Spanish site, I expect data to be in Spanish, by default).
Change Globalize.locale (and notI18n.locale) to change model translation.
Reset I18n.locale and Globalize.locale after every test. In rspec
RSpec.configure do |config|
config.after(:each) do
I18n.locale = :en
Globalize.locale = :en
end
end
Either use subdomain, or subfolder in the url to refer to the locale of the app.
Use a language parameter to specify the language of the data.
When you work on your rails app, you will probably use default_url_options to use I18n.locale as the default locale parameter for your route / path helpers. However this doesn't work in tests. I picked up a solution from this github issue on rspec-rails.
I monkey patch ActionDispatch::Routing::RouteSet like so:
class ActionDispatch::Routing::RouteSet
def url_for_with_locale_fix(options={})
url_for_without_locale_fix(options.merge(:locale => I18n.locale))
end
alias_method_chain :url_for, :locale_fix
end
Set up a translate helper t as a wrapper around the I18n.t method
module I18nHelper
def t string, options = {}
I18n.t string, options
end
end
RSpec.configure do |config|
config.include I18nHelper
end
This is mini-pattern I use. You can check it out: http://developers-note.blogspot.com/2012/01/rails-i18n-good-practice.html

Resources