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

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.

Related

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 mountable engine: how should apps set configuration variables?

I have a mountable engine called Blog that apps can use.
What's the best way to allow apps that use the engine to set a configuration variable like site_name (so that the engine can display it in the design)?
Update:
I've seen some gems create a 'config/initializers/gem_name.rb' file. Is there any specification on how to:
Create a file like that on the engine's side
Copy it into the app's side
How to access those set variables on the engine's side?
I tried creating Blog.site_name = "My Site" in the app's config/initializers/blog.rb file but get an Undefined method error.
Figured out an even better solution that also allows you to set default values (incase the app using the engine doesn't specify a config)...
Create config variables in your app's /config/initializers/blog.rb like this:
Blog.setup do |config|
config.site_name = "My Site Name"
end
In your engine's /lib/blog/engine.rb set default values like this:
module Blog
self.mattr_accessor :site_name
self.site_name = "Site Name"
# add default values of more config vars here
# this function maps the vars from your app into your engine
def self.setup(&block)
yield self
end
end
Now you can simply access the config variables in your engine like this:
Blog.site_name
Much cleaner.
After a lot of testing and looking into existing gems, here is what works in Rails 4:
Considering your engine's name is Blog:
In your engine's /lib/blog/engine.rb put in this:
module Blog
def self.setup(&block)
#config ||= Blog::Engine::Configuration.new
yield #config if block
#config
end
def self.config
Rails.application.config
end
end
In your app, create a file called /config/initalizers/blog.rb and set config vars like this:
Blog.setup do |config|
config.testingvar = "asdfasdf"
end
Then you can access these config variables ANYWHERE in your engine like this:
Blog.config.testingvar
Hope this helps someone. There is very little documentation on this right now so it was all trial and error.
I know this is a fairly old post, but in the event someone in the future finds this, I'd like to recommend the Angostura gem for passing dependencies into a Rails engine. To use it, assuming my engine is called 'Blog' and I want to access a variable called 'site_name', the engine's lib/blog.rb looks something like:
require "blog/engine"
require "angostura"
module Blog
include Angostura::Dependencies
dependency :site_name
end
In my main app, in config/initializers/blog.rb, I added
Blog.setup do |config|
config.site_name = "My site name"
end
Now, I can access site_name in my engine by calling Blog.site_name.
I'd like to point out that defaults are also supported, so you could do something like dependency site_name: 'Default site name' in lib/blog.rb. Furthermore, you can pass in whole classes as dependencies by sending it stringified classnames, like config.my_class = 'MyClass'.
For posterity, in order to use the gem, I added s.add_dependency "angostura", "0.6.0" in my engine's gemspec, and then ran bundle install.

Locale fallback from country to language without having to define each individually

I am localizing an app with the default rails I18n with globalize3 as the back-end.
Is it possible to set a locale with a country code (ie :fr-CA) to fallback to its specific language (:fr) before going to the default fallback automatically? I know its possible to set each locale/country manually with
config.i18n.fallbacks = {'fr-CA' => 'fr'}
But it would be nice to not have to add each fallback manually and have this behaviour automatic.
To achieve precisely this I have an initializer with
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
See the source code for more info.
Edit:
This reminds me, there is an annoying bug in the ActionView LookupContext which prevents this from working for localized views (though it works correcly for locale files). I see it still hasn't been fixed. Basically, if you have any localized views (help pages for example, which are unsuitable to store in locale files due to their length) then a fr-CA locale will not fall back to a view called help.fr.html.erb. You either have to name the file help.fr-CA.html.erb or, which is what I have done, monkeypatch the LookupContext with another initializer, sort of like this:
module ActionView
class LookupContext
# Override locale= to also set the I18n.locale. If the current I18n.config object responds
# to original_config, it means that it's has a copy of the original I18n configuration and it's
# acting as proxy, which we need to skip.
def locale=(value)
if value
config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
config.locale = value[0,2] # only use first part of the locale in lookups
end
super(#skip_default_locale ? I18n.locale : default_locale)
end
end
end
Another edit: Note that the patch is rather crude and breaks full locale lookups, going straight for just the language. If you need to also have fully matching views (language-REGION) you'll need to improve my code!

Internationalizing whole text with markup in Rails 3

What's the best practice for internationalizing, say, a Terms of Service document in Rails 3? I can think of two options:
Creating a partial for each locale and choosing which one to load according to the current user's locale.
<li><%= I18n.t :tos_paragraph_1 %></li><li><%= I18n.t :tos_paragraph_2 %></li>
None of these seems like a good solution. Any ideas?
There are a few solutions, but if I were doing this for a production project, I would probably do something like the following:
Create files for your translated Terms of Services in config/locales/terms/, naming them terms.en.html, replacing en with the locale for each translation and html with the format of the file (e.g. you could use Haml, Markdown, etc.).
Create the following helper methods (put them in app/helpers/application_helper.rb to use them everywhere, but you can put them in whatever helper file you need/want):
def localized_document_for(document, locale)
raise ArgumentError.new('nil is not a valid document') if document.nil?
raise I18n::InvalidLocale.new('nil is not a valid locale') if locale.nil?
localized_document = path_for_localized_document(document, locale)
raise MissingTranslationData unless File.exists?(localized_document)
# If you're using Markdown, etc. replace with code to parse/format your document
File.open(localized_document).readlines.join
end
def path_for_localized_document(document, locale)
"#{Rails.root}/config/locales/#{document}/#{document}.#{locale.to_s}.html"
end
Now, in your views, you can use localized_document_for('terms', I18n.locale) any time you need to get the contents of the Terms of Service in the user's language. Now the code you're using to fetch the document is DRY (you can easily fetch other documents by creating another directory in config/locales and changing the value of the document argument), and your translated documents are stored in their own directory and can easily be edited (and don't depend on YAML, which brings no value to storing a single document in a file).
Note that if you wanted to do it the "Rails 3 Way," you could use the I18n::Backend::Chain (see https://github.com/svenfuchs/i18n/blob/master/lib/i18n/backend/chain.rb), and pass in I18n::Backend::Simple.new along with a custom backend that reads the files as necessary, but for a one-off deal I believe the helpers work sufficiently.
Creating a partial for each language is definitly undry.
The Rails way is the second option you suggest with a .yml for each language.
The doc provides great examples to let you organize the yml according to each page. Thus you have shortcuts for all your variables.
See http://guides.rubyonrails.org/i18n.html

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