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 +82 -1
- data/VERSION +1 -1
- data/app/models/translation.rb +36 -27
- data/lib/{trendi18n.rb → trendi18n/backend/trendi18n.rb} +11 -5
- data/lib/trendi18n/handler.rb +17 -0
- data/rails/init.rb +7 -3
- data/spec/test_application/app/controllers/application_controller.rb +2 -0
- data/spec/test_application/app/controllers/translations_controller.rb +4 -0
- data/spec/test_application/app/views/translations/edit.html.erb +1 -1
- data/spec/test_application/app/views/translations/locales_list.html.erb +5 -0
- data/spec/test_application/config/routes.rb +1 -1
- data/spec/test_application/features/managing_translations.feature +19 -0
- data/spec/test_application/features/static_translation.feature +1 -1
- data/spec/test_application/features/step_definitions/translations_steps.rb +10 -0
- data/spec/test_application/features/support/paths.rb +2 -0
- data/spec/test_application/spec/backend_spec.rb +5 -12
- data/spec/test_application/spec/models/translation_spec.rb +11 -6
- metadata +6 -4
- /data/lib/{commands.rb → trendi18n/generator/commands.rb} +0 -0
data/README.rdoc
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
= Introduction
|
2
2
|
|
3
|
-
Trendi18n is database backend for Rails' i18n.
|
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
|
+
0.9.2
|
data/app/models/translation.rb
CHANGED
@@ -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
|
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
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
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
|
43
|
-
Translation.
|
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
|
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
|
@@ -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
|
-
|
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
|
|
@@ -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
|
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
|
73
|
+
I18n.backend.up_to_date?.should == true
|
79
74
|
end
|
80
75
|
|
81
|
-
it "should be up-to-date
|
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 ==
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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.
|
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-
|
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
|
File without changes
|