I18n Rails list of supported locales (constants) - ruby-on-rails

So following is what I would like to do, be able to access I18n locale as a constant.For example:
if (I18n.locale == I18n.locales.en)
puts "You are using viewing page in english"
end
Is there a way to access these constants (I18n.locales.en is just example to clarify)? I can always write
if (I18n.locale.to_s == "en")
but I would like to avoid that. Since code is less readable with that approach.

You could shorten your second statement to be if (I18n.locale == :en) rather than converting to a string, however that's still missing the point of using Rails' locale support. For example, with this yml file:
en:
hello: "Hello world!"
fr:
hello: "Bonjour tout le monde !"
You should just be able to do this without any conditional statements:
puts I18n.translate(:hello)
and it would give you the appropriate translation.
If you really wanted to be able to do something like your first example, then you could override method_missing on the Symbol class. If you did this:
class Symbol
def method_missing(method_name, *arguments)
if method_name.to_s[-1,1] == "?"
self.to_s == method_name.to_s[0..-2]
else
super
end
end
end
Then you could do this:
if I18n.locale.en?
puts "english"
end

Related

How to translate a website in all languages?

I'm trying to translate my website in all languages supported by Google Translate.
I'm using Ruby on Rails 6, and want to do it as a translation backend, but this is not specific to Ruby or Ruby on Rails.
When I had to support 6 languages I would correct the mistakes myself but I can't
I tried different things but my latest strategy has been storing everything in the database:
class ActiveRecordBackend
include I18n::Backend::Base
include I18n::Backend::Transliterator
SEPARATE_INTERPOLATIONS = /(?<interpolation>%{[^}]+})|(?<text>[^%]+)/
NETWORK_ERRORS = [SocketError, Errno::EHOSTUNREACH].freeze
LOCALES_PATH = Rails.root.join("lib/data/locales.yml")
LOCALES = YAML.safe_load(LOCALES_PATH.read).map(&:to_struct).sort_by(&:name)
LOCALE_NAMES = LOCALES.map(&:locale).map(&:to_sym)
def available_locales
LOCALE_NAMES
end
def reload!
#translations = nil
self
end
def initialized?
!#translations.nil?
end
def init_translations
#translations = Translation.to_hash
end
def translations(do_init: false)
init_translations if do_init || !initialized?
#translations ||= {}
end
private
def lookup(locale, key, _scope = [], _options = {})
Translation.find_by(locale: locale, key: key)&.value ||
store_translation(locale: locale, key: key)
end
def store_translation(locale:, key:)
default = Translation.find_by(locale: I18n.default_locale, key: key)
return unless default
translated_value =
easy_translate(default.value, from: I18n.default_locale, to: locale)
return unless translated_value
Translation.find_or_create_by(
locale: locale,
key: key,
value: translated_value
)
translated_value
end
def easy_translate(original, from:, to:)
original
.scan(SEPARATE_INTERPOLATIONS)
.map do |interpolation, text|
next interpolation if interpolation
spaces_before = text.scan(/\A */).first
spaces_after = text.scan(/ *\z/).first
translated_text =
EasyTranslate.translate(text, from: from, to: to).strip
"#{spaces_before}#{translated_text}#{spaces_after}"
end
.join
rescue *NETWORK_ERRORS, EasyTranslate::EasyTranslateException
nil
end
end
But I get things like
"<b>7976membri attivi tra cui1in linea<br>562attività con5945partecipazioni"
for italian
instead of:
"<b>7976</b> membres actifs dont <b>1</b> en ligne <br><b>562</b> activités avec <b>5945</b> participations"
for french
And I also don't handle returning a group of translations like t(".js").
How would you do it?
How would you do it?
I wouldn't do it.
If your website only natively supports a few languages (e.g. English) and a user wants to view it in an unsupported language (e.g. Italian), then let the user apply Google Translation themselves.
There's a very popular plugin to do this. But, like you found already, it won't always give perfect results: Sometimes it can mess up your page layout, in addition to just giving sub-optimal translations due to mis-interpreted context.
If you discover a magic way to accurately apply website translations in the backend to all possible languages and contexts, without breaking the UI, then congratulations -- you'll soon be incredibly wealthy.
Agree with #GiacomoCatenazzi, it looks very unprofessional to have obvious spelling mistakes. If you have to translate the page, I recommend you use I18n and do it manually.
If you feel like you have to use GT, I would do something like this:
Create the I18n files for each language
Only manually populate the english one
Create a class which reads in the english version of the I18n file to a hash.
Loop through all the files you want to populate, if the key does not exist you should use the GT api to translate and populate the files where the key does not exist.
Create a cron job and run the class everyday.
You can improve the amount of requests in step 4 as much as you want, with some dedication it should be possible to limit the requests to the amount of languages you support.
I found a solution:
def easy_translate(original, from:, to:)
interpolations_in_original = original.scan(INTERPOLATION)
spaces_before = original.scan(/\A */).first
spaces_after = original.scan(/ *\z/).first
translated_text = EasyTranslate.translate(original, from: from, to: to).strip
translated_text = translated_text.gsub("% {", "%{")
bad_interpolations = translated_text.scan(INTERPOLATION)
interpolations_in_original.size.times do |index|
translated_text.gsub!(bad_interpolations[index], interpolations_in_original[index])
end
"#{spaces_before}#{translated_text}#{spaces_after}"
rescue *NETWORK_ERRORS, EasyTranslate::EasyTranslateException
nil
end
Can be improved obviously like actually replacing each interpolation by the corresponding correct one and keeping capitalization

Replacing {phrase} with phrase in rails

I'd like to search and replace any occurrence of {phrase} with with phrase using rails (erb.html file). Multiple phrases will need to be substituted, and the phrases aren't known in advance.
Full Example:
Hi {guys}, I really like {ruby on rails}
Needs to become
Hi guys, ruby on rails
This is for a user-generated content site (GMT)
it's simple regexp, just use
your_string.gsub(/{(.*?)}/, '\\1')
Example:
"{aaa} is not {bbb} you know".gsub(/{(.*?)}/, '\\1')
will produce
aaa is not bbb you know
You can do this using gsub
irb(main):001:0> str = " I have written this phrase statement, I want to replace occurences of all phrase with other statement"
=> " I have written this phrase statement, I want to replace occurences of all phrase with other statement"
irb(main):002:0> str.gsub("phrase",'phrase')
=> " I have written this phrase statement, I want to replace occurences of all phrase with other statement"
A better way to do this will be to use a Markdown output engine (Redcarpet being one of the most robust)
You'd have to create a custom renderer:
#lib/custom_renderer.rb
class AutoLinks < Redcarpet::Render::HTML
def auto_link(phrase) #-> will need to search through content. Can research further
link_to phrase, "/#{phrase}"
end
end
#controller
markdown = Redcarpet::Markdown.new(AutoLinks, auto_link: "ruby on rails")
Just use a helper in your erb. For example:
tag_helper.rb:
module TagHelper
def atag(phrase)
"<a href='/#{phrase}'>#{phrase}</a>"
end
end
some.html.erb:
<%= atag('guys')%>

List Rails I18n strings with full keys

I'd like to be able to generate a complete list of all the I18n keys and values for a locale including the full keys. In other words if I have these files:
config/locales/en.yml
en:
greeting:
polite: "Good evening"
informal: "What's up?"
config/locales/second.en.yml
en:
farewell:
polite: "Goodbye"
informal: "Later"
I want the following output:
greeting.polite: "Good evening"
greeting.informal: "What's up?"
farewell.polite: "Goodbye"
farewell.informal: "Later"
How do I do this?
Once loaded into memory it's just a big Hash, which you can format any way you want. to access it you can do this:
I18n.backend.send(:translations)[:en]
To get a list of available translations (created by you or maybe by plugins and gems)
I18n.available_locales
Nick Gorbikoff's answer was a start but did not emit the output I wanted as described in the question. I ended up writing my own script get_translations to do it, below.
#!/usr/bin/env ruby
require 'pp'
require './config/environment.rb'
def print_translations(prefix, x)
if x.is_a? Hash
if (not prefix.empty?)
prefix += "."
end
x.each {|key, value|
print_translations(prefix + key.to_s, value)
}
else
print prefix + ": "
PP.singleline_pp x
puts ""
end
end
I18n.translate(:foo)
translations_hash = I18n.backend.send(:translations)
print_translations("", translations_hash)
Here's a working version of a method you can use to achieve your desired output
def print_tr(data,prefix="")
if data.kind_of?(Hash)
data.each do |key,value|
print_tr(value, prefix.empty? ? key : "#{prefix}.#{key}")
end
else
puts "#{prefix}: #{data}"
end
end
Usage:
$ data = YAML.load_file('config/locales/second.en.yml')
$ print_tr(data)
=>
en.farewell.polite: "Goodbye"
en.farewell.informal: "Later"

i18n sync of locals yaml keys

Similar question, but for java, Keeping i18n resources synced
How to keep the i18n yaml locals' keys in sync? i.e. when a key is added to en.yml, how to get those to nb.yml or ru.yml?
if I add the key my_label: "some text in english" next to my_title: "a title" I'd like to get this to my other locals I specify, as I can't do all translations and it should fall back to english in other languages
e.g en.yml
somegroup:
my_tile: "a title in english"
my_label: "some text in english"
othergroup:
...
I'd like go issue a command and get the whole key and value inject into the norwegian translation and the corresponding position, if missing. Then git diff would show all translations to do in this language.
nb.yml
somegroup:
my_tile: "En tittel på norsk"
+ my_label: "some text in english"
othergroup:
...
Are there any gems that do something like this? If you think it's a good idea, maybe I should take the time to make it myself. Other approaches?
Try the i18n_translation_spawner gem, it could be helpful.
I will check i18n_translation_spawner gem. In case that someone needs a not-so-fast but do the job, i use this script:
First we extend the Hash class in order to support deep_merge and to replace all their leaf values with some string.
require 'yaml'
class Hash
def deep_merge(hash)
target = dup
hash.keys.each do |key|
if hash[key].is_a? Hash and self[key].is_a? Hash
target[key] = target[key].deep_merge(hash[key])
next
end
target[key] = hash[key]
end
target
end
def fill_all_values value
each_key do |key|
if self[key].is_a?(String)
store(key,value)
else
self[key].fill_all_values value
end
end
end
end
Now we can use our merger of translations:
def merge_yaml_i18n_files(locale_code_A,locale_code_B,untranslated_message)
hash_A = YAML.load_file("i18n/#{locale_code_A}.yml")
hash_B = YAML.load_file("i18n/#{locale_code_B}.yml")
hash_A_ut = Marshal.load(Marshal.dump(hash_A))
hash_A_ut.fill_all_values(untranslated_message)
hash_B_ut = Marshal.load(Marshal.dump(hash_B))
hash_B_ut.fill_all_values(untranslated_message)
hash_A = hash_B_ut.deep_merge(hash_A)
hash_B = hash_A_ut.deep_merge(hash_B)
puts hash_A.to_yaml
puts hash_B.to_yaml
end
And finally, we call this method with:
merge_yaml_i18n_files('en','es','untranslated')
If we apply this function in the following i18n files:
es.yaml
test:
hello: Hola
only_es: abc
en.yaml
test:
hello: Hello
only_en: def
The result will be:
es.yaml
test:
hello: Hola
only_en: untranslated
only_es: abc
en.yaml
test:
hello: Hello
only_en: def
only_es: untranslated
You can use i18n-tasks gem for this.
It scans calls such as I18n.t('some.key') and provides reports on key usage, missing, and unused keys. It can also can pre-fill missing keys, including from Google Translate, and it can remove unused keys as well.

Whats the best way to put a small ruby app online?

I have a small ruby application I wrote that's an anagram searcher. It's for learning ruby, but I would like to put it up online for personal use. I have some experience with Rails, and many here have recommended Sinatra. I'm fine with either, but I cannot find any information on how to use a text file instead of a database.
The application is quite simple, validates against a text file of a word list, then finds all anagrams. I have been assuming that this should be quite simple, but I'm stuck on importing that textfile into Rails (or Sinatra if i choose that way). In the Rails project, I have placed the textfile in the lib directory.
Unfortunately, even though the path appears to be correct in Rails, I get an error:
no such file to load -- /Users/court/Sites/cvtest/lib/english.txt
(cvtest is the name of the rails project)
Here is the code. It works great by itself:
file_path = '/Users/court/Sites/anagram/dictionary/english.txt'
input_string = gets.chomp
# validate input to list
if File.foreach(file_path) {|x| break x if x.chomp == input_string}
#break down the word
word = input_string.split(//).sort
# match word
anagrams = IO.readlines(file_path).partition{
|line| line.strip!
(line.size == word.size && line.split(//).sort == word)
}[0]
#list all words except the original
anagrams.each{ |matched_word| puts matched_word unless matched_word == input_string }
#display error if
else
puts "This word cannot be found in the dictionary"
end
Factor the actual functionality (finding the anagrams) into a method. Call that method from your Web app.
In Rails, you'd create a controller action that calls that method instead of ActiveRecord. In Sinatra, you'd just create a route that calls the method. Here's a Sinatra example:
get '/word/:input'
anagrams = find_anagrams(params[:input])
anagrams.join(", ")
end
Then, when you access the http://yourapp.com/word/pool, it will print "loop, polo".
I know the question is marked as answered, but I prefer the following, as it uses query parameters rather than path based parameters, which means you can pass the parameters in using a regular GET form submission:
require 'rubygems'
require 'sinatra'
def find_anagrams word
# your anagram method here
end
get '/anagram' do
#word = params['word']
#anagrams = find_anagrams #word if #word
haml :anagram
end
And the following haml (you could use whatever template language you prefer). This will give you an input form, and show the list of anagrams if a word has been provided and an anagram list has been generated:
%h1
Enter a word
%form{:action => "anagram"}
%input{:type => "text", :name => "word"}
%input{:type => "submit"}
- if #word
%h1
Anagrams of
&= #word
- if #anagrams
%ul
- #anagrams.each do |word|
%li&= word
- else
%p No anagrams found
With sinatra, you can do anything. These examples doesn't even require sinatra, you could roll your own rack interface thing.
require 'rubygems'
require 'sinatra'
require 'yaml'
documents = YAML::load_file("your_data.yml")
Or:
require 'rubygems'
require 'sinatra'
content = Dir[File.join(__DIR__, "content/*.textile)].map {|path|
content = RedCloth(File.read(path)).to_html
}
Etcetera.

Resources