Skip to content

Instantly share code, notes, and snippets.

@amardaxini
Created August 17, 2011 08:56
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save amardaxini/1151126 to your computer and use it in GitHub Desktop.
Save amardaxini/1151126 to your computer and use it in GitHub Desktop.
redis i18n
<tr>
<td><%=@key.split(".").drop(1) %></td>
<td><%= @value%></td>
</tr>
# This class is helpful for redis i18n implementation
#
#---
# en:
# text_menu_contacts: "Contacts"
# text_menu_accounts: "Accounts"
# text_menu_opportunity: "Opportunities"
# errors:
# template:
# header: "%{count} errors prohibited this %{model} from being saved"
class DynamicI18n
#$redis = Redis.new
# yaml to redis for i18n
# file name is "/abc/en.yml"
def self.i18n_yaml_to_redis(file_name)
# for picking up initial key for iterating
# TODO may be it can be automated further
i18n_key = file_name.split(".").first.split("/").last
#i18n_key = "en"
if File.exist?(file_name)
i18n_yml = YAML::load(File.open(file_name))
i18n_yml[i18n_key].each do |k,v|
# if yaml data contain further nested then create nestedkey or add key to redis
if i18n_yml[i18n_key][k].is_a?(Hash)
self.add_value_to_redis("#{i18n_key}",i18n_yml[i18n_key])
else
create("#{i18n_key}.#{k}","#{v}")
end
end
end
end
# en.
# errors:
# template:
# header: "%{count} errors prohibited this %{model} from being saved"
# THis method generates folloeing key and set its respective value
#en.errors.template.header="%{count} errors prohibited this %{model} from being saved"
def self.add_value_to_redis(i18n_key,k)
k.each do |key,value|
if k[key].is_a?(Hash)
self.add_value_to_redis(i18n_key+"."+key,k[key])
else
create("#{i18n_key}.#{key}","#{value}")
end
end
end
# Add to en(master) it will add to all company
# Company is a redis hash which contain company id or company name as per requirement
# and its i18n value liek us_20
# any data is added to master it will find all the respective company and add data
def self.i18n_add_to_master(key,val)
create("#{key}","#{val}")
key = key.split(".").drop(1)
# find out all company and add to all company
# like after create callback
company_array = $redis.hgetall("company")
company_array.each do |ckey,cvalue|
create("#{cvalue}.#{key}",val)
end
end
# for removing data from master and company
def self.i18n_remove_from_master(key)
remove("#{key}")
#key = en.abc.xyz
key = key.split(".").drop(1)
# key abc.xyz
company_array = $redis.hgetall("company")
company_array.each do |ckey,cvalue|
destroy("#{cvalue}.#{key}")
end
end
# add to redis
def self.create(key,value)
$redis.set(key,value)
end
# remove from redis
def self.destroy(key)
$redis.del(key)
end
# get data from redis
def self.find(key)
$redis.get(key)
end
#This contain name = "company name or id",value en_20
def self.add_company(name,value)
$redis.hset("company",name,value)
end
#This contain name = "company name or id"
def self.remove_company(name)
$redis.hdel("company",name)
end
# find out word and replace it
def self.search_and_replace_word(search_value,replace_value,i18n="*")
i18n_keys = find_all(i18n)
i18n_keys.each do |i18n_key|
value = $redis.get(i18n_key)
value.gsub!(/\b#{search_value}\b/,replace_value)
$redis.set(i18n_key,value)
end
end
# add master clone to another company
# i18n_key is us_20
def self.clone_master_data_for_company(i18n_key)
# find out all master keys i.e en
master_keys= find_all("en.*")
master_keys.each do |key|
clone_key = "#{i18n_key}.#{key.split('.').drop(1)}"
create(clone_key,$redis.get(key))
end
end
def self.find_all(key)
$redis.keys(key)
end
end
class DynamicI18nsController < ApplicationController
before_filter :load_redis
layout 'admin'
def index
@companies = @redis.hgetall("company").to_a
#companies =Company.find(:all,companies.keys).collect{|c| [c.name,c.dynamic_label.file_name]}
# @companies = Company.find(companies.keys)
if params[:company].present?
@company_key = params[:company]
else
@company_key = "en"
end
@translations = @redis.keys("#{@company_key}.*")
end
def add
@key = params[:i18n]+"."+params[:key]
if params[:i18n] == "en"
DynamicI18n.i18n_add_to_master(@key,params[:value])
else
DynamicI18n.create(@key,params[:value])
end
@value = DynamicI18n.find(@key)
respond_to do |format|
format.js
end
end
def modify
DynamicI18n.create(params[:id],params[:value])
render :text=>DynamicI18n.find(params[:id])
end
def remove
DynamicI18n.destroy(params[:key])
end
def replace_word
if params[:company]
search_key = "#{params[:company]}.*"
company_key = params[:company]
else
search_key = "*"
company_key = "en"
end
DynamicI18n.search_and_replace_word(params[:search_value],params[:replace_value],search_key)
redirect_to dynamic_i18ns_path(:company=>company_key)
end
private
def load_redis
@redis = $redis
end
end
# Assuming redis on default localhost and port 5678
module I18nBackend
def self.connect
$redis = Redis.new
I18n.backend = I18n::Backend::Chain.new(I18n::Backend::KeyValue.new($redis), I18n.backend)
end
end
if defined?(PhusionPassenger)
PhusionPassenger.on_event(:starting_worker_process) do |forked|
if forked
# We're in smart spawning mode. If we're not, we do not need to do anything
I18nBackend.connect
end
end
else
I18nBackend.connect
end
#TRANSLATION_STORE = Redis.new
#I18n.backend =I18n::Backend::KeyValue.new(TRANSLATION_STORE)
#I18n.backend = I18n::Backend::Chain.new(I18n::Backend::KeyValue.new(TRANSLATION_STORE), I18n.backend)
<%= javascript_include_tag "jquery.jeditable.js" %>
<div>
<% form_tag('/dynamic_i18ns',:method=>:get,:id=>"company_select_form") do %>
<%= select_tag :company,"<option value=en>Master Data</option>"+options_for_select(@companies,:selected=>@company_key )%>
<% end %>
<% form_tag('/dynamic_i18ns/replace_word',:id=>"replace_form") do %>
<strong>Search</strong> <%= text_field_tag :search_value %>
<strong>Replace</strong> <%=text_field_tag :replace_value %>
<%= hidden_field_tag :company,@company_key %>
<%= submit_tag "Find & Replace" %>
<% end %>
<strong>Key</strong> <%= text_field_tag :i18n_key %>
<strong>Value</strong> <%=text_field_tag :i18n_value %>
<%= submit_tag "Add" ,:id=>"add_i18n"%>
</div>
<table>
<thead>
<tr>
<th> Key </th>
<th> ORIGINAL </th>
<th> Values </th>
</tr>
<tbody id="i18n_table_body">
<% @translations.each do |key| %>
<tr>
<td>
<%i18n_key = key.split(".").drop(1)%>
<%= key.split(".").drop(1) %>
</td>
<td> <%= @redis["en.#{i18n_key}"]%></td>
<td class="edit_in_place" id="<%= key %>" ><%= @redis[key]%></td>
</tr>
<% end %>
</tbody>
</thead>
</table>
<script type="text/javascript">
function in_line_edit()
{
jQuery('.edit_in_place').editable('/dynamic_i18ns/modify', {
indicator : 'Saving...',
tooltip : 'Click to edit...',
id:$(this).attr('id'),
submit : 'OK',
width: '150',
cancel: "cancel"
});
}
in_line_edit();
$("#company").live("change",function(){
$("#company_select_form").submit();
});
$("#add_i18n").live("click",function(){
$.ajax({
type: "POST",
url: "/dynamic_i18ns/add",
data: "key="+$("#i18n_key").val()+"&value="+$("#i18n_value").val()+"&i18n="+"<%= @company_key %>",
success: function(html){
/*html content response of city_list.xxx */
$("#i18n_table_body").append(html);
$("#i18n_key").val("");
$("#i18n_value").val("");
}
});
});
</script>
I18n implementation using redis
Reference
https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale
http://ruby-i18n.org/
http://railscasts.com/episodes/138-i18n
http://redis.io/
http://redis.io/commands
https://github.com/ezmobius/redis-rb
Older Implementation
When company is created they can choose I18n and if they want customize version of labeling they can do it.
dynamic_label model is used to store i18n name like "us".
if it is customize then new file is created with i18nname_company_id like us_20
and i18n is reloaded again or required server to be again restart after changing data every time.
Current Implementation
Instead of file we used redis database to handle this kind of stuff.
This help to allow change data and reflect imidiately to client.
We are useing reids-rb gem (ruby interface for redis).
There are mainly 3 files to do this job
1) config/initializers/i18n_backend.rb
It helps us to connect redis server and setting I18n Backend with redis
More info view railscasts
2) app/models/dynamic_i18n.rb
This is like model.
It helps us to CRUD Operation to redis and many more.
3) app/controllers/dynamic_i18ns_controller.rb
There are certain Scenario we need to handle like
What about older data? How we migrate older data? what if new company is added?
Older data is migrate to redis by using rake task
rake locale_to_redis
This task do following things
1) Initially add en and us locale file to redis
2) Find out all dynamic label of each company
3) If label is customized and its respective file is present then
It adds data to redis
Add company to redis hash("company") (company_id=>"#{i18nname}_#{company_id}"
4) If label is customized and file is not present or no customizaion is present then
It copies master data i.e en data to respective company with suffix with _company_id("us_20")
Add company to redis hash("company") (company_id=>"#{i18nname}_#{company_id}"
Update dynamic_label i18n name with #{i18nname}_#{company_id}
What if new company is added ?
It copies master data i.e en data to respective company with suffix with _company_id("us_20")
Add company to redis hash("company") (company_id=>"#{i18nname}_#{company_id}"
What happens if we added new data to master?
If data is added to master it will imediately reflected all existing company data.
TODO above things you need to start redis server
Download redis and install(http://redis.io/download)
start redis server using redis-server
If you want to start redis on background
nohup path_of_redis-server >> /dev/null 2>&1 &
In Production if we are using passenger and forking method is smart
then passenger spawn a new process with same redis connection
so mutex issue comes in picture to handle this each time process is forked add new redis connection
If this is not done passenger spawn process hike cpu and memory usage abruptly
to understand more on this
http://ryreitsma.blogspot.com/2011/07/redis-and-phusion-passenger-reconnect.html
https://github.com/ezmobius/redis-rb/issues/117
http://www.modrails.com/documentation/Users%20guide%20Apache.html#_example_1_memcached_connection_sharing_harmful
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment