Browse files

Revive Tolk

  • Loading branch information...
1 parent f765f17 commit 1c8ba781f796b22d681413dca380bcd875ac347c @lifo lifo committed Apr 7, 2010
View
1 .gitignore
@@ -0,0 +1 @@
+tmp/**/*
View
6 app/controllers/locales_controller.rb
@@ -4,10 +4,12 @@ def index
end
def show
- @locale = Locale.find(params[:id])
+ @locale = Locale.find_by_name!(params[:id])
+ render :primary_locale if @locale.primary?
end
def create
- redirect_to(Locale.create(params[:locale]))
+ Locale.create!(params[:locale])
+ redirect_to :action => :index
end
end
View
9 app/controllers/phrases_controller.rb
@@ -2,13 +2,4 @@ class PhrasesController < ApplicationController
def index
@phrases = Phrase.all
end
-
- def create
- translation_parameters = params[:phrase].delete(:translation)
-
- phrase = Phrase.all.create(params[:phrase])
- translation = Translation.create(translation_parameters.merge(:phrase => phrase))
-
- redirect_to(translation.locale)
- end
end
View
16 app/controllers/translations_controller.rb
@@ -1,7 +1,21 @@
class TranslationsController < ApplicationController
+ before_filter :ensure_no_primary_locale, :only => :update
+
def create
+ locale_id = params[:translation].delete(:locale_id)
+
+ @locale = Locale.find(locale_id)
+ @translation = @locale.translations.new(params[:translation])
+
+ if @translation.save
+ flash[:notice] = 'Translation saved'
+ else
+ flash[:alert] = 'Translation could not be saved'
+ end
+
+ redirect_to @locale
end
-
+
def update
@translation = Translation.find(params[:id])
@translation.update_attributes!(params[:translation])
View
34 app/models/locale.rb
@@ -2,19 +2,35 @@ class Locale < ActiveRecord::Base
has_many :phrases, :through => :translations
has_many :translations, :include => :phrase
- LOCALES_CONFIG_PATH = "#{Rails.root}/config/locales"
+ cattr_accessor :locales_config_path
+ self.locales_config_path = "#{Rails.root}/config/locales"
+
+ cattr_accessor :primary_locale_name
+
+ include Tolk::Sync
+
+ validates_uniqueness_of :name
class << self
- def dump_all(to = LOCALES_CONFIG_PATH)
- all.each do |locale|
+ def primary_locale
+ raise "Primary locale is not set. Please set Locale.primary_locale_name in your application's config file" unless self.primary_locale_name
+
+ Locale.find_or_create_by_name(self.primary_locale_name)
+ end
+
+ def secondary_locales
+ Locale.all - [primary_locale]
+ end
+
+ def dump_all(to = self.locales_config_path)
+ secondary_locales.each do |locale|
File.open("#{to}/#{locale.name}.yml", "w+") do |file|
YAML.dump(locale.to_hash, file)
end
end
end
end
-
def phrases_with_translation
translations.collect do |translation|
translation.phrase.translation = translation
@@ -35,7 +51,15 @@ def to_hash
end
end }
end
-
+
+ def to_param
+ name.parameterize
+ end
+
+ def primary?
+ name == self.class.primary_locale_name
+ end
+
private
def unsquish(string, value)
if string.is_a?(String)
View
12 app/models/phrase.rb
@@ -1,5 +1,13 @@
class Phrase < ActiveRecord::Base
- has_many :translations
-
+ has_many :translations, :dependent => :destroy do
+ def primary
+ to_a.detect {|t| t.locale_id == Locale.primary_locale.id}
+ end
+
+ def secondary(locale)
+ to_a.detect {|t| t.locale_id == locale.id}
+ end
+ end
+
attr_accessor :translation
end
View
2 app/models/translation.rb
@@ -1,4 +1,6 @@
class Translation < ActiveRecord::Base
+ validates_presence_of :text
+
belongs_to :phrase
belongs_to :locale
end
View
3 app/views/layouts/application.html.erb
@@ -11,6 +11,9 @@
</head>
<body>
+ <ul>
+ <li><%= link_to "Home", locales_path %></li>
+ </ul>
<%= yield %>
</body>
</html>
View
22 app/views/locales/index.html.erb
@@ -1,7 +1,23 @@
<h1>Locales</h1>
<ul>
- <% for locale in @locales %>
- <li><%= link_to locale.name, locale %></li>
+ <% @locales.each do |locale| %>
+ <li>
+ <%= link_to locale.name, locale %><%= " <b>(primary)</b>" if locale.primary? %>
+ </li>
<% end %>
-</ul>
+</ul>
+
+<h1>Create a new Locale</h1>
+
+<% form_for(Locale.new) do |f| %>
+ <%= f.error_messages %>
+
+ <p>
+ <%= f.label :name %><br />
+ <%= f.text_field :name %>
+ </p>
+ <p>
+ <%= f.submit 'Create' %>
+ </p>
+<% end %>
View
19 app/views/locales/primary_locale.html.erb
@@ -0,0 +1,19 @@
+<h1><%= @locale.name %> <b>(primary)</b></h1>
+
+<h4>Tolk treats primary translations as readonly</h4>
+
+<h2>All translations</h2>
+
+<table>
+ <tr><th>Phrase</th><th>Translation</th></tr>
+ <% @locale.phrases_with_translation.each do |phrase| %>
+ <tr>
+ <td>
+ <%= phrase.key %>
+ </td>
+ <td>
+ <%= phrase.translation.text %>
+ </td>
+ </tr>
+ <% end %>
+</table>
View
26 app/views/locales/show.html.erb
@@ -11,13 +11,14 @@
<%= phrase.key %>
</td>
<td>
- <% remote_form_for(Translation.new(:locale => @locale, :phrase => phrase)) do |f| %>
+ <% form_for(Translation.new(:locale => @locale, :phrase => phrase)) do |f| %>
<%= f.hidden_field :locale_id %>
<%= f.hidden_field :phrase_id %>
<%= f.text_field :text %>
- <%= f.submit "Update" %>
+ <%= f.submit "Update" %> ( <i>Hint:</i> <%= phrase.translations.primary.text -%> )
<% end %>
+
</td>
</tr>
<% end %>
@@ -36,28 +37,9 @@
<td>
<% remote_form_for(phrase.translation) do |f| %>
<%= f.text_field :text %>
- <%= f.submit "Update" %>
+ <%= f.submit "Update" %> ( <i>Hint:</i> <%= phrase.translations.primary.text -%> )
<% end %>
</td>
</tr>
<% end %>
</table>
-
-<table>
-<tr><th>Phrase</th><th>Translation</th></tr>
-<% form_for([ @application, Phrase.new ]) do |f| %>
- <tr>
- <td>
- <%= f.text_field :key %>
- </td>
- <td>
- <% f.fields_for(Translation.new) do |ff| %>
- <%= ff.hidden_field :locale_id, :value => @locale.id %>
- <%= ff.text_field :text %>
- <% end %>
-
- <%= f.submit "Create" %>
- </td>
- </tr>
-<% end %>
-<table>
View
2 config/environment.rb
@@ -5,7 +5,7 @@
# ENV['RAILS_ENV'] ||= 'production'
# Specifies gem version of Rails to use when vendor/rails is not present
-RAILS_GEM_VERSION = '2.2.0' unless defined? RAILS_GEM_VERSION
+RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION
# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
View
10 config/routes.rb
@@ -1,7 +1,7 @@
ActionController::Routing::Routes.draw do |map|
- map.resources :locales, :has_many => :translations
- map.resources :phrases, :has_many => :translations
- map.resources :translations
-
- map.root :locales
+ map.with_options(:path_prefix => '/tolk') do |tolk|
+ tolk.resources :locales, :has_many => :translations
+ tolk.resources :phrases, :has_many => :translations
+ tolk.resources :translations
+ end
end
View
2 lib/tolk.rb
@@ -0,0 +1,2 @@
+module Tolk
+end
View
69 lib/tolk/sync.rb
@@ -0,0 +1,69 @@
+module Tolk
+ module Sync
+ def self.included(base)
+ base.send :extend, ClassMethods
+ end
+
+ module ClassMethods
+ def sync!
+ raise "Primary locale is not set. Please set Locale.primary_locale_name in your application's config file" unless self.primary_locale_name
+
+ translations = read_primary_locale_file
+ sync_phrases(translations)
+ end
+
+ def read_primary_locale_file
+ primary_file = "#{self.locales_config_path}/#{self.primary_locale_name}.yml"
+ raise "Primary locale file #{primary_file} does not exists" unless File.exists?(primary_file)
+
+ flat_hash(YAML::load(IO.read(primary_file))[self.primary_locale_name])
+ end
+
+ private
+
+ def sync_phrases(translations)
+ primary_locale = self.primary_locale
+ secondary_locales = self.secondary_locales
+
+ # Handle deleted phrases
+ translations.present? ? Phrase.destroy_all(["phrases.key NOT IN (?)", translations.keys]) : Phrase.destroy_all
+
+ phrases = Phrase.all
+
+ translations.each do |key, value|
+ # Create phrase and primary translation if missing
+ existing_phrase = phrases.detect {|p| p.key == key} || Phrase.create!(:key => key)
+ translation = existing_phrase.translations.primary || primary_locale.translations.build(:phrase_id => existing_phrase.id)
+
+ # Update primary translation if it's been changed
+ if value.present? && translation.text != value
+ translation.text = value
+ translation.save!
+ end
+
+ # Make sure the translation record exists for all the locales
+ # secondary_locales.each do |locale|
+ # existing_translation = existing_phrase.translations.detect {|t| t.locale_id == locale.id }
+ # locale.translations.create!(:phrase_id => existing_phrase.id) unless existing_translation
+ # end
+ end
+ end
+
+ def flat_hash(data, prefix = '', result = {})
+ data.each do |key, value|
+ current_prefix = prefix.present? ? "#{prefix}.#{key}" : key
+
+ if value.is_a?(Hash)
+ flat_hash(value, current_prefix, result)
+ else
+ result[current_prefix] = value
+ end
+ end
+
+ result
+ end
+
+ end
+
+ end
+end
View
6 test/locales/basic/da.yml
@@ -0,0 +1,6 @@
+---
+da:
+ cozy: Hyggeligt
+ nested:
+ hello_world: Nedarvet Hej Verden
+ hello_world: Hej Verden
View
6 test/locales/basic/en.yml
@@ -0,0 +1,6 @@
+---
+en:
+ nested:
+ hello_world: Nested Hello World
+ hello_country: Nested Hello Country
+ hello_world: Hello World
View
3 test/locales/basic/se.yml
@@ -0,0 +1,3 @@
+---
+se:
+ hello_world: Hejsan Verdon
View
5 test/locales/sync/en.yml
@@ -0,0 +1,5 @@
+---
+en:
+ nested:
+ hello_country: Nested Hello Country
+ hello_world: Hello World
View
14 test/unit/locale_test.rb
@@ -4,7 +4,7 @@ class LocaleTest < ActiveSupport::TestCase
test "turning locale without nested phrases into a hash" do
assert_equal({ "se" => { "hello_world" => "Hejsan Verdon" } }, locales(:se).to_hash)
end
-
+
test "turning locale with nested phrases into a hash" do
assert_equal({ "en" => {
"hello_world" => "Hello World",
@@ -14,23 +14,23 @@ class LocaleTest < ActiveSupport::TestCase
}
}}, locales(:en).to_hash)
end
-
+
test "phrases without translations" do
assert locales(:en).phrases_without_translation.include?(phrases(:cozy))
end
test "dumping all locales to yml" do
begin
- FileUtils.mkdir_p(Rails.root + "/tmp/locales")
- Locale.dump_all(Rails.root + "/tmp/locales")
-
+ FileUtils.mkdir_p(RAILS_ROOT + "/tmp/locales")
+ Locale.dump_all(RAILS_ROOT + "/tmp/locales")
+
%w( en da se ).each do |locale|
assert_equal \
- File.read(fixture_path + "/locales/#{locale}-yml"),
+ File.read(RAILS_ROOT + "/test/locales/basic/#{locale}.yml"),
File.read(RAILS_ROOT + "/tmp/locales/#{locale}.yml")
end
ensure
- FileUtils.rm_rf(Rails.root + "/tmp/locales")
+ FileUtils.rm_rf(RAILS_ROOT + "/tmp/locales")
end
end
end
View
118 test/unit/sync_test.rb
@@ -0,0 +1,118 @@
+require 'test_helper'
+require 'fileutils'
+
+class SyncTest < ActiveSupport::TestCase
+ def setup
+ Locale.delete_all
+ Translation.delete_all
+ Phrase.delete_all
+
+ Locale.locales_config_path = RAILS_ROOT + "/test/locales/sync"
+ Locale.primary_locale_name = 'en'
+ end
+
+ def test_flat_hash
+ data = {'home' => {'hello' => 'hola', 'sidebar' => {'title' => 'something'}}}
+ result = Locale.send(:flat_hash, data)
+
+ assert_equal 2, result.keys.size
+ assert_equal ['home.hello', 'home.sidebar.title'], result.keys.sort
+ assert_equal ['hola', 'something'], result.values.sort
+ end
+
+ def test_sync_creates_locale_phrases_translations
+ Locale.sync!
+
+ # Created by sync!
+ primary_locale = Locale.find_by_name!(Locale.primary_locale_name)
+
+ assert_equal ["Hello World", "Nested Hello Country"], primary_locale.translations.map(&:text).sort
+ assert_equal ["hello_world", "nested.hello_country"], Phrase.all.map(&:key).sort
+ end
+
+ def test_sync_deletes_stale_translations_for_secondary_locales_on_delete_all
+ spanish = Locale.create!(:name => 'es')
+
+ Locale.sync!
+
+ phrase = Phrase.all.detect {|p| p.key == 'hello_world'}
+ hola = spanish.translations.create!(:text => 'hola', :phrase => phrase)
+
+ # Mimic deleting all the translations
+ Locale.expects(:read_primary_locale_file).returns({})
+ Locale.sync!
+
+ assert_equal 0, Phrase.count
+ assert_equal 0, Translation.count
+
+ assert_raises(ActiveRecord::RecordNotFound) { hola.reload }
+ end
+
+ def test_sync_deletes_stale_translations_for_secondary_locales_on_delete_some
+ spanish = Locale.create!(:name => 'es')
+
+ Locale.sync!
+
+ phrase = Phrase.all.detect {|p| p.key == 'hello_world'}
+ hola = spanish.translations.create!(:text => 'hola', :phrase => phrase)
+
+ # Mimic deleting 'hello_world'
+ Locale.expects(:read_primary_locale_file).returns({'nested.hello_country' => 'Nested Hello World'})
+ Locale.sync!
+
+ assert_equal 1, Phrase.count
+ assert_equal 1, Translation.count
+ assert_equal 0, spanish.translations.count
+
+ assert_raises(ActiveRecord::RecordNotFound) { hola.reload }
+ end
+
+ def test_sync_handles_deleted_keys_and_updated_translations
+ Locale.sync!
+
+ # Mimic deleting 'nested.hello_country' and updating 'hello_world'
+ Locale.expects(:read_primary_locale_file).returns({"hello_world" => "Hello Super World"})
+ Locale.sync!
+
+ primary_locale = Locale.find_by_name!(Locale.primary_locale_name)
+
+ assert_equal ['Hello Super World'], primary_locale.translations.map(&:text)
+ assert_equal ['hello_world'], Phrase.all.map(&:key).sort
+ end
+
+ def test_sync_doesnt_mess_with_existing_translations
+ spanish = Locale.create!(:name => 'es')
+
+ Locale.sync!
+
+ phrase = Phrase.all.detect {|p| p.key == 'hello_world'}
+ hola = spanish.translations.create!(:text => 'hola', :phrase => phrase)
+
+ # Mimic deleting 'nested.hello_country' and updating 'hello_world'
+ Locale.expects(:read_primary_locale_file).returns({"hello_world" => "Hello Super World"})
+ Locale.sync!
+
+ hola.reload
+ assert_equal 'hola', hola.text
+ end
+
+ def test_dump_all_after_sync
+ spanish = Locale.create!(:name => 'es')
+
+ Locale.sync!
+
+ phrase = Phrase.all.detect {|p| p.key == 'hello_world'}
+ hola = spanish.translations.create!(:text => 'hola', :phrase => phrase)
+
+ tmpdir = RAILS_ROOT + "/tmp/sync/locales"
+ FileUtils.mkdir_p(tmpdir)
+ Locale.dump_all(tmpdir)
+
+ spanish_file = "#{tmpdir}/es.yml"
+ data = YAML::load(IO.read(spanish_file))['es']
+ assert_equal ['hello_world'], data.keys
+ assert_equal 'hola', data['hello_world']
+ ensure
+ FileUtils.rm_f(tmpdir)
+ end
+end

0 comments on commit 1c8ba78

Please sign in to comment.