I got a little bit of a challenge but I don't know where to start. Long story short, I'm trying to make a method that will automatically translate a record from its model via Deepl or Google Translate.
I've got something working but I want to refactor it so it gets more versatile:
def translate
texts = [self.title_fr, self.desc_fr, self.descRequirements_fr, self.descTarget_fr, self.descMeet_fr, self.descAdditional_fr]
translations = DeepL.translate texts, 'FR', 'EN'
self.update(title_en: translations[0], descRequirements_en: translations[2], descTarget_en: translations[3], descMeet_en: translations[4], descAdditional_en: translations[5])
end
Hopefully this is self explanatory.
I would love to have a method/concern working like such :
def deeplTranslate(record, attributes)
// Code to figure out
end
and use it like such : deeplTranslate(post, ['title', 'desc', 'attribute3']). And that will translate the attributes and save the translated attributes to the database in en language.
Thanks in advance to anyone that can point me to a valid direction to go towards.
Okay, I actually managed to create an auto translate method for active record :
def deeplTranslate(record, attributes, originLang, newLang)
keys = attributes.map{|a| record.instance_eval(a + "_#{originLang}")}
translations = DeepL.translate keys, originLang, newLang
new_attributes = Hash.new
attributes.each_with_index do |a, i|
new_attributes[a + "_#{newLang}"] = translations[i].text
end
record.update(new_attributes)
end
Maybe it can get cleaner... But it's working : )
Related
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
I am working on an app in Rails 4 using i18n-active_record 0.1.0 to keep my translations in the database rather than in a .yml-file. It works fine.
One thing that I am struggling with, however, is that each translation record is one record per locale, i.e.
#1. { locale: "en", key: "hello", value: "hello")
#2. { locale: "se", key: "hello", value: "hej")
which makes updating them a tedious effort. I would like instead to have it as one, i.e.:
{ key: "hello", value_en: "hello", value_se: "hej" }
or similar in order to update all instances of one key in one form. I can't seem to find anything about that, which puzzles me.
Is there any way to easily do this? Any type of hacks would be ok as well.
You could make an ActiveRecord object for the translation table, and then create read and write functions on that model.
Read function would pull all associated records then combine them into a single hash.
Write function would take your single hash input and split them into multiple records for writing/updating.
I ended up creating my own Translation functionality using Globalize. It does not explicitly rely on I18n so it is a parallell system but it works, although not pretty and it is not a replacement to I18n but it has the important functionality of being able to easily add a locale and handle all translations in one form.
Translation model with key:string
In Translation model:
translates :value
globalize_accessors :locales => I18n.available_locales, :attributes => [:value]
In ApplicationHelper:
def t2(key_str)
key_stringified = key_str.to_s.gsub(":", "")
t = Transl8er.find_by_key(key_stringified)
if t.blank?
# Translation missing
if t.is_a? String
return_string = "Translation missing for #{key_str}"
else
return_string = key_str
end
else
begin
return_string = t.value.strip
rescue
return_string = t.value
end
end
return_string
end
I have created a small Ruby class here:
class Star
#Star initialization
def initialize(star, number)
#star = star
#number = number
end
end
and I am looking to initialize a class called Solar System with 100 stars. This is what I have done and it doesn't seem to be working. Any help would be greatly appreciated.
require_relative 'star.rb'
class SolarSystem
#Initialize Game
def initialize(partOfSolarSystem)
#partOfSolarSystem = partOfSolarSystem
#stars_array = []
for i in 0..99
stars_array = Star.new('unknown_star',i)
end
end
def show_solar_system
#code here to show all the initialized stars in solar system
end
end
I can't seem to get it to initialize the array in the constructor. I would then like to be able to print out all of the elements in the stars array. Any help with this would be greatly appreciated.
Also in an effort to eventually move this to a database with rails or something of that nature, should I be looking to hash this or will this be easily converted to mySQL or another DB with some helper functions? I would eventually like to write this into rails and have a dynamic website for it.
Once again, thanks very much.
Your problem is assigning a new value to #stars_array variable on each iteration. There are multiple ways to deal with it:
#stars_array = (0..99).map { |i| Star.new('unknown_star',i) }
By the way, there is a couple of design issues (just for your attention):
Why variable is called stars_array, not just stars?
Why would ever instance of Star class have some object named #star inside? Recursion? :) Seems like #name would be proper and more clear attribute's name.
Don't miss indentation.
EDIT: About DB-mapping. Most common way - inherit both classes from ActiveRecord::Base, and create one-to-many relation from solar system to stars. Each class will have it's own table. Takes absolutely no efforts.
You are assigning the new object every time round the loop. The fix is to append the new object:
#stars_array << Star.new('unknown_star',i)
Or, if you prefer words rather than symbols:
#stars_array.push(Star.new('unknown_star',i))
Or, to be more terse:
100.times {|i| #stars_array << Star.new('unknown_star',i) }
A few things to fix to make it work. In your loop you're assigning a new value to the array rather than appending to it. Secondly, in your loop you're using a local variable stars_array instead of the instance variable #stars_array.
Your initialize method should look like this:
def initialize(part_of_solar_system)
#part_of_solar_system = part_of_solar_system
#stars_array = []
for i in 0..99
#stars_array << Star.new('unknown_star', i)
end
end
Also, you might want to revisit your Ruby idioms, like preferring snake_case to camelCase for variable names and avoiding for loops in favor of each, e.g.
def initialize(part_of_solar_system)
#part_of_solar_system = part_of_solar_system
#stars_array = []
(0..99).each { |i| #stars_array << Star.new('unknown_star', i) }
end
I've created a form with about 40 fields available to edit, I'm trying to save them to a database using the controller. I currently have this code:
c = Form.find(params[:id])
if c
params.each do |k,v|
c.k = params[:v]
end
Which doesn't work, I get this error: undefined method 'k='
if I was going to write them all out manually it would look like this:
c = Form.find(params[:id])
if c
c.title = params[:title]
c.reference = params[:reference]
....
etc.
Assuming that you're trying to update the attributes on your Form record based on what gets passed into params, try this as a basic outline:
c = Form.find_by_id(params[:id])
if c
params.each do |k, v|
c[k] = v
end
c.save!
end
Your original code's use of params[:v] was probably not doing what you were intending, and you really meant for it to be params[:k] instead. However there's actually no need to look up the value for that key inside the loop like that because you already have the value at hand in v.
Here's a quick rundown on the ways of interacting with ActiveRecord attributes: http://www.davidverhasselt.com/2011/06/28/5-ways-to-set-attributes-in-activerecord/
i dont know what you are trying todo but your code seems to be very odd. Solution is as follow
c.send "#{k}=", params[:v]
What about
c = Form.find(params[:id])
c.update_attributes(params[:form])
Note that I guessed the [:form] part in the second line, it depends on your form. check your html source, and see if your fields are something like this:
<input name="form[field_name]" ...
As you see, name contains an "array like" form. Check your HTML source and adapt (so if its name="foo[field_name]", you need to use c.update_attributes(params[:foo]))
I have an array with model attributes (these model attributes are columns in DB table). I am trying to iterate through this array and automatically create a record which I would like to save to DB table, something like this:
columns.each_with_index do |c, i|
user.c = data[i]
puts user.c
end
user is model.
But if I try the snippet above, I get
undefined method `c=' for #<User:0x007f8164d1bb80>
I've tried also
columns.each_with_index do |c, i|
user."#{c}" = data[i]
puts user."#{c}"
end
But this doesn't work as well.
Data in columns array are taken from form that sends user, so I want to save only data that he send me, but I still cannot figure it out...
I would like to ask you for help... thank you in advance!
user.send("#{c}=".to_sym, data[i])
Also, you can access the attributes as a hash.
user.attributes[c] = data[i]
The best thing would probably be to build a hash and to use update_attributes:
mydata = {}
columns.each_with_index{|c, i| mydata[c] = data[i]}
user.update_attributes(mydata)
this way you retain the protections provided by attr_accessible.
If this is actually in a controller, you can just make use of some basic rails conventions and build the User record like this:
#user = User.new(params[:user])
if #user.save
# do something
else
# render the form again
end
Although you can set the values using send, I agree with #DaveS that you probably want to protect yourself via attr_accessibles. If your planning to use Rails 4, here's a good overview.