trendi18n 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,6 +1,14 @@
1
1
  = Introduction
2
2
 
3
- Trendi18n is database backend for Rails' i18n. Trendi18n offers some administration functionality, which offers:
3
+ Trendi18n is database backend for Rails' i18n.
4
+
5
+ Default backend provided by Rails is a great and simple solution. It is however developer oriented: keeps translations in the files, require repository access for translators and their supervision.
6
+
7
+ Trendi18n is targeted at projects where translation must be part of administration interface and where it should be possible to modify translations in live application.
8
+
9
+ = Features
10
+
11
+ Trendi18n offers some administration functionality, which offers:
4
12
 
5
13
  * paginated list of translations in database (with locale and translation status conditions)
6
14
  * autoassigned status for translations:
@@ -14,6 +22,79 @@ Trendi18n is database backend for Rails' i18n. Trendi18n offers some administra
14
22
 
15
23
  Real-time updating store_translations
16
24
 
25
+ = Install
26
+
27
+ Trendi18n is hosted on gemcutter. Add gemcutter to your gems source and type:
28
+
29
+ gem install trendi18n
30
+
31
+ = How to use
32
+
33
+ If you want to use trendi18n in your app you should do three steps:
34
+
35
+ * Add to apps config/environment.rb file:
36
+
37
+ config.gem "trendi18n", :lib => false
38
+
39
+ * Run trendi18n generator. Type in your app main directory:
40
+
41
+ ruby script/generate trendi18n
42
+
43
+ * Add to your ApplicationController:
44
+
45
+ include Trendi18n::Handler
46
+
47
+ = Changelog
48
+
49
+ === 0.9.2
50
+
51
+ * Backend reloading system (multi-threading supported)
52
+ * Getting info of available locales system (multi-threading supported)
53
+ * More documentation
54
+ * Nicer structure of files and directories in lin directory
55
+
56
+ = How it works
57
+
58
+ Trendi18n is still supporting default source of translations - localizations files.
59
+ This translations are loaded to standard I18n::Backend::Simple translation store
60
+ system (called 'cache'). When I18n.translate is used:
61
+
62
+ * First of all, trendi18n lookup translation in cache.
63
+ * If translation is not found in cache then trendi18n lookup translation in db (using Translation model) and add it to cache
64
+ * If translation is not found in db then create empty translation in db (and add it to cache)
65
+
66
+ === Translation model
67
+
68
+ The soul of trendi18n is Translation model used for storing translations in db. If you have naver used rails i18n, you should first read guide: http://guides.rubyonrails.org/i18n.html
69
+ This list of translation's fields (with short description) should be useful:
70
+
71
+ * *key* - used to identification translation
72
+ * *locale* - locale of translation
73
+ * *scope* - used to identification translation. The same key can be located in various scopes, so there we be various translations. Simply, scope is something like prefix for key. Use dot to separate subscopes
74
+ * *default* - return where there is no translation for this key
75
+ * *translation* - standard translation for this key
76
+ * *zero* - "plural" form of translation, when count is used and it equals 0
77
+ * *one* - "plural" form of translation, when count is used and it equals 1
78
+ * *many* - "plural" form of translation, when count is used and it's greater then 1
79
+ * *few* - currently not used "plural" form of translation, when count is used and it's greater then 1 but smaller then "few + 1"
80
+ * *state* - state of translation. Available values
81
+
82
+ * new - new translation
83
+ * unfinished - translation with some plural forms
84
+ * finished - finished translation
85
+
86
+ * *timestamps*
87
+
88
+ You should use Translation model to managing translations stored in database.
89
+
90
+ === Getting more
91
+
92
+ You can get more information about trendi18n reading our documentation and source
93
+
94
+ = Bugs, new features etc.
95
+
96
+ If you find some bugs or you think trendi18n should do something differently or has new features please open new issue on github: http://github.com/bragi/trendi18n/issues
97
+
17
98
  = Copyright
18
99
 
19
100
  Copyright © 2009 Łukasz Piestrzeniewicz. See LICENSE for details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.1
1
+ 0.9.2
@@ -1,8 +1,6 @@
1
1
  class Translation < ActiveRecord::Base
2
2
  before_validation :set_state
3
3
  before_validation :set_defaults
4
- after_save :reload_backend
5
-
6
4
 
7
5
  named_scope :untranslated, :conditions => {:state => %w(new unfinished)}
8
6
  named_scope :translated, :conditions => {:state => "finished"}
@@ -14,19 +12,50 @@ class Translation < ActiveRecord::Base
14
12
 
15
13
  @@locales = []
16
14
 
17
- # return locales if their exists and translations are up-to-date. In other case assign are locales values from db to locales and return this value
15
+ # return locales which translations are stored in db
18
16
  def self.get_locales
19
17
  @@locales
20
18
  end
21
19
 
20
+ # read locales which translation are stored in db
22
21
  def self.set_locales
23
22
  @@locales = self.all(:select => "DISTINCT(locale)", :order => "locale ASC").map { |obj| obj.locale }
24
23
  end
25
24
 
26
- # This method is running after save translation into db and it is reloading I18n backend (trendi18n)
27
- def reload_backend
28
- I18n.backend.reload!
29
- Translation.set_locales
25
+ # Set @read_time, date and time of first database read on current update.
26
+ # @read_time is used to get translations cache up-to-date status
27
+ def self.set_read_time
28
+ @read_time = Time.zone.now if @read_time.nil?
29
+ end
30
+
31
+ # Clear value of @read_time. It is used durnig backend reload!
32
+ def self.clear_read_time
33
+ @read_time = nil
34
+ end
35
+
36
+ # Return date and time of last translations update in database. It is used to get
37
+ # translations cache up-to-date status and we need to reload backend only after
38
+ # update existing translations (new translations will be cached when they will be used
39
+ # first time), so we are looking for the biggest update time in translations that
40
+ # updated_at is greater then created_at
41
+ def self.update_time
42
+ self.exists?(["updated_at > created_at"]) ? self.first(:conditions => "updated_at > created_at", :order => "updated_at DESC").updated_at : Time.zone.at(0)
43
+ end
44
+
45
+ def self.update_locales_time
46
+ self.exists? ? self.first(:order => "updated_at DESC").updated_at : Time.zone.at(0)
47
+ end
48
+
49
+ # Checking translations cache up-to-date status. We need to reload backend when
50
+ # cache is not up-to-date. It's going on when so existing translation was updated.
51
+ # We can't reload backend using after_update callback becouse there can be many
52
+ # proccesses running our apps and all of them must be reloaded, not only the updater.
53
+ def self.up_to_date?
54
+ @read_time.nil? ? true : self.update_time.to_i < @read_time.to_i
55
+ end
56
+
57
+ def self.locales_up_to_date?
58
+ @read_time.nil? ? true : self.update_locales_time.to_i < @read_time.to_i
30
59
  end
31
60
 
32
61
  # auto-magic finder using string normalized key to find translation. The first value in scope is the locale, the last is the key and all between are scopes
@@ -53,25 +82,6 @@ class Translation < ActiveRecord::Base
53
82
  self.translation = nil if self.translation.blank? # set nil to translation if it is blank (empty string)
54
83
  end
55
84
 
56
- # time of the last db update
57
- def self.base_updated_at
58
- self.exists?(["updated_at > created_at"]) ? self.first(:order => "updated_at DESC", :conditions => "updated_at > created_at").updated_at : Time.at(0)
59
- end
60
-
61
- # time of the db read on current db update
62
- def self.read_base
63
- @base_read_at = Time.zone.now if @base_read_at.nil?
64
- end
65
-
66
- def self.clear_base_read_at
67
- @base_read_at = nil
68
- end
69
-
70
- # db is up-to-date when it wasn't read or read time is larger then update time
71
- def self.up_to_date?
72
- @base_read_at.nil? || @base_read_at.to_i > self.base_updated_at.to_i
73
- end
74
-
75
85
  # don't be nil if there is "{{count}}" in key
76
86
  def with_count?
77
87
  /\{\{count\}\}/.match(self.key)
@@ -102,7 +112,6 @@ class Translation < ActiveRecord::Base
102
112
 
103
113
  # look up for translation and return it, if exists, or create if not
104
114
  def self.lookup(locale, key, default = nil, scope = nil)
105
- self.read_base
106
115
  scope = I18n.send(:normalize_translation_keys, locale, key, scope)
107
116
  locale = scope.delete_at(0).to_s
108
117
  key = scope.delete_at(scope.size - 1).to_s
@@ -3,17 +3,20 @@ module Trendi18n
3
3
  module Backend
4
4
 
5
5
  class Trendi18n < I18n::Backend::Simple
6
- delegate :up_to_date?, :to => Translation # delgate up_to_date? method to Translation model
6
+
7
+ delegate :up_to_date?, :to => Translation
8
+ delegate :locales_up_to_date?, :to => Translation
7
9
 
8
10
  # return available locales, based on informaton form Translation model
9
11
  def available_locales
12
+ Translation.set_read_time
10
13
  Translation.get_locales
11
14
  end
12
15
 
13
16
  # translate key in locale using options
14
17
  def translate(locale, key, options = {})
15
18
  raise InvalidLocale.new(locale) if locale.nil?
16
-
19
+ Translation.set_read_time
17
20
  #if key is an array then as a result run yourself for all subkey in key
18
21
  return key.map { |subkey| translate(locale, subkey, options)} if key.is_a?(Array)
19
22
 
@@ -37,10 +40,13 @@ module Trendi18n
37
40
  entry = interpolate(locale, entry, values) # run interpolation for translation
38
41
  end
39
42
 
40
-
41
43
  def reload!
42
- super # run standard I18n::Backend::Simple reload! method
43
- Translation.clear_base_read_at # and clear information about time of last translation's base read
44
+ super
45
+ Translation.clear_read_time
46
+ end
47
+
48
+ def locales_reload!
49
+ Translation.set_locales
44
50
  end
45
51
 
46
52
  protected
@@ -0,0 +1,17 @@
1
+ module Trendi18n
2
+ module Handler
3
+
4
+ def self.included(subclass)
5
+ subclass.class_eval do
6
+ before_filter :translations_cache_updater
7
+
8
+ # check translations cache up-to-date status and reload backend or/and locales when needed
9
+ def translations_cache_updater
10
+ I18n.backend.reload! unless I18n.backend.up_to_date?
11
+ I18n.backend.locales_reload! unless I18n.backend.locales_up_to_date?
12
+ end
13
+ end
14
+ end
15
+
16
+ end
17
+ end
data/rails/init.rb CHANGED
@@ -1,5 +1,9 @@
1
- require 'trendi18n'
2
- require "commands"
1
+ require 'trendi18n/backend/trendi18n'
2
+ require 'trendi18n/generator/commands'
3
+ require 'trendi18n/handler'
3
4
  require 'file'
4
5
 
5
- I18n.backend = Trendi18n::Backend::Trendi18n.new
6
+ I18n.backend = Trendi18n::Backend::Trendi18n.new
7
+
8
+
9
+
@@ -5,6 +5,8 @@ class ApplicationController < ActionController::Base
5
5
  helper :all # include all helpers, all the time
6
6
  protect_from_forgery # See ActionController::RequestForgeryProtection for details
7
7
 
8
+ include Trendi18n::Handler
9
+
8
10
  # Scrub sensitive parameters from your log
9
11
  # filter_parameter_logging :password
10
12
  end
@@ -5,6 +5,10 @@ class TranslationsController < ApplicationController
5
5
  @translations = Translation.localization(params[:localization]).send(params[:condition].downcase.to_sym) if ["untranslated", "translated", "all"].include?(params[:condition].downcase)
6
6
  end
7
7
 
8
+ def locales_list
9
+ @locales = I18n.backend.available_locales
10
+ end
11
+
8
12
  def new
9
13
  @translation = Translation.new
10
14
  end
@@ -1,6 +1,6 @@
1
1
  <%= error_messages_for 'translation' %>
2
2
 
3
- <% form_for @translation, :url => {:action => "update"} do %>
3
+ <% form_for @translation do %>
4
4
  <%= render :partial => "form" %>
5
5
  <%= submit_tag("Save") %>
6
6
  <% end %>
@@ -0,0 +1,5 @@
1
+ <ul>
2
+ <% for locale in @locales %>
3
+ <li /><%= h locale %>
4
+ <% end %>
5
+ </ul>
@@ -39,7 +39,7 @@ ActionController::Routing::Routes.draw do |map|
39
39
  # Note: These default routes make all actions in every controller accessible via GET requests. You should
40
40
  # consider removing or commenting them out if you're using named routes and resources.
41
41
 
42
- map.resources :translations
42
+ map.resources :translations, :collection => {:locales_list => :get}
43
43
 
44
44
  # map.connect ':controller/:action/:id'
45
45
  # map.connect ':controller/:action/:id.:format'
@@ -59,4 +59,23 @@ Scenario: Editing translations
59
59
  And I should see "Key1NewTranslation"
60
60
  And I should see "Key1Many"
61
61
 
62
+ Scenario: List of available locales
63
+ Given I have translated "Key1" to "Key1PL" in "pl" locale
64
+ And I have translated "Key1" to "Key1EN" in "en" locale
65
+ When I am on the list of available locales
66
+ Then I should see "en"
67
+ And I should see "pl"
68
+ Given I have translated "Key1" to "Key1NL" in "nl" locale
69
+ When I go to the list of available locales
70
+ Then I should see "en"
71
+ And I should see "pl"
72
+ And I should see "nl"
73
+ Given I have relocalized "Key1" from "nl" to "es"
74
+ When I go to the list of available locales
75
+ Then I should see "en"
76
+ And I should see "pl"
77
+ And I should see "es"
78
+ And I should not see "nl"
79
+
80
+
62
81
 
@@ -3,7 +3,7 @@ Feature: Static translation
3
3
  As a language idiot
4
4
  I want to be given static page elements in a selected language
5
5
 
6
- #Scenario: Simple translations test
6
+ Scenario: Simple translations test
7
7
  Given I have translated "Key1" to "Key1Translation" in "en" locale
8
8
  And I have tranlated "Key2" to "Key2Translation" with scope "scope1.subscope1" in "en" locale
9
9
  And I have translated "Key3" to "Klucz3Tłumaczenie" in "pl" locale
@@ -6,6 +6,16 @@ Given /^I have tranlated "([^\"]*)" to "([^\"]*)" with scope "([^\"]*)" in "([^\
6
6
  Translation.create!(:key => key, :translation => translation, :locale => locale, :scope => scope)
7
7
  end
8
8
 
9
+ Given /^I have relocalized "([^\"]*)" from "([^\"]*)" to "([^\"]*)"$/ do |key, old_locale, new_locale|
10
+ Translation.first(:conditions => {:key => key, :locale => old_locale}).update_attribute('locale', new_locale)
11
+ end
12
+
13
+ When /^I reloading locales$/ do
14
+ Translation.set_locales
15
+ end
16
+
17
+
18
+
9
19
 
10
20
 
11
21
 
@@ -25,6 +25,8 @@ module NavigationHelpers
25
25
  edit_translation_path
26
26
  when /the translations test page/
27
27
  translation_path :id => 1
28
+ when /the list of available locales/
29
+ locales_list_translations_url
28
30
 
29
31
  # Add more mappings here.
30
32
  # Here is a more fancy example:
@@ -1,9 +1,5 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- I18n.backend = Trendi18n::Backend::Trendi18n.new
4
-
5
-
6
-
7
3
  describe Trendi18n::Backend::Trendi18n do
8
4
 
9
5
  before do
@@ -43,7 +39,6 @@ describe Trendi18n::Backend::Trendi18n do
43
39
  I18n.reload!
44
40
  I18n.t("test_key")
45
41
  end
46
-
47
42
  describe "up-to-date status" do
48
43
 
49
44
  it "should be up-to-date when there is no translations" do
@@ -73,16 +68,17 @@ describe Trendi18n::Backend::Trendi18n do
73
68
  I18n.backend.up_to_date?.should == true
74
69
  end
75
70
 
76
- it "should still be up-to-date when we was looking for new translation" do
71
+ it "should be up-to-date when we was looking for new translation" do
77
72
  I18n.t("non_existing_key")
78
- I18n.backend.up_to_date? == true
73
+ I18n.backend.up_to_date?.should == true
79
74
  end
80
75
 
81
- it "should be up-to-date even translation is updated after use" do
76
+ it "should not be up-to-date if translation is updated after use" do
82
77
  I18n.t("test_key")
83
78
  @translation.update_attribute("created_at", 2.minutes.ago)
84
- I18n.backend.up_to_date?.should == true
79
+ I18n.backend.up_to_date?.should == false
85
80
  end
81
+
86
82
  end
87
83
  end
88
84
 
@@ -151,8 +147,5 @@ describe Trendi18n::Backend::Trendi18n do
151
147
  it "should translate to hash of translations"
152
148
 
153
149
  end
154
-
155
-
156
150
  end
157
-
158
151
  end
@@ -16,6 +16,7 @@ describe Translation do
16
16
  end
17
17
 
18
18
 
19
+
19
20
  describe "state assigment" do
20
21
 
21
22
  before do
@@ -91,12 +92,16 @@ describe Translation do
91
92
  Translation.find_by_string_normalized_key(translation.key).should == translation
92
93
  end
93
94
 
94
- it "should return array of all locale values form db" do
95
- Translation.read_base
96
- Translation.create!(:key => "key", :locale => "pl")
97
- Translation.create!(:key => "key", :locale => "en")
98
- Translation.create!(:key => "key", :locale => "nl")
99
- Translation.get_locales.should == ["en", "nl", "pl"]
95
+ describe "locales tools" do
96
+
97
+ it "should return array of all locale values form db" do
98
+ Translation.create!(:key => "key", :locale => "pl")
99
+ Translation.create!(:key => "key", :locale => "en")
100
+ Translation.create!(:key => "key", :locale => "nl")
101
+ Translation.set_locales
102
+ Translation.get_locales.should == ["en", "nl", "pl"]
103
+ end
104
+
100
105
  end
101
106
 
102
107
  it "should return correct plural form (using count argument)" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trendi18n
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Misiurek
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2009-12-15 00:00:00 +01:00
14
+ date: 2009-12-30 00:00:00 +01:00
15
15
  default_executable:
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
@@ -42,9 +42,10 @@ files:
42
42
  - app/models/translation.rb
43
43
  - generators/trendi18n/templates/migrations/create_translations.rb
44
44
  - generators/trendi18n/trendi18n_generator.rb
45
- - lib/commands.rb
46
45
  - lib/file.rb
47
- - lib/trendi18n.rb
46
+ - lib/trendi18n/backend/trendi18n.rb
47
+ - lib/trendi18n/generator/commands.rb
48
+ - lib/trendi18n/handler.rb
48
49
  - rails/init.rb
49
50
  - spec/test_application/README
50
51
  - spec/test_application/Rakefile
@@ -54,6 +55,7 @@ files:
54
55
  - spec/test_application/app/views/translations/_form.html.erb
55
56
  - spec/test_application/app/views/translations/edit.html.erb
56
57
  - spec/test_application/app/views/translations/index.html.erb
58
+ - spec/test_application/app/views/translations/locales_list.html.erb
57
59
  - spec/test_application/app/views/translations/new.html.erb
58
60
  - spec/test_application/app/views/translations/show.html.erb
59
61
  - spec/test_application/config/boot.rb