translate-rails3 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README +62 -0
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/app/controllers/translate_controller.rb +165 -0
- data/app/helpers/translate_helper.rb +45 -0
- data/app/views/layouts/translate.html.erb +359 -0
- data/app/views/translate/_pagination.html.erb +24 -0
- data/app/views/translate/index.html.erb +114 -0
- data/config/routes.rb +5 -0
- data/init.rb +1 -0
- data/lib/tasks/translate.rake +178 -0
- data/lib/translate.rb +8 -0
- data/lib/translate/file.rb +35 -0
- data/lib/translate/keys.rb +152 -0
- data/lib/translate/log.rb +35 -0
- data/lib/translate/routes.rb +11 -0
- data/lib/translate/storage.rb +28 -0
- data/spec/controllers/translate_controller_spec.rb +130 -0
- data/spec/file_spec.rb +54 -0
- data/spec/files/translate/app/models/article.rb +12 -0
- data/spec/files/translate/app/views/category.erb +1 -0
- data/spec/files/translate/app/views/category.html +1 -0
- data/spec/files/translate/app/views/category.html.erb +1 -0
- data/spec/files/translate/app/views/category.rhtml +5 -0
- data/spec/files/translate/public/javascripts/application.js +1 -0
- data/spec/keys_spec.rb +179 -0
- data/spec/log_spec.rb +47 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/storage_spec.rb +33 -0
- data/translate-rails3.gemspec +77 -0
- metadata +118 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
class Translate::Log
|
2
|
+
attr_accessor :from_locale, :to_locale, :keys
|
3
|
+
|
4
|
+
def initialize(from_locale, to_locale, keys)
|
5
|
+
self.from_locale = from_locale
|
6
|
+
self.to_locale = to_locale
|
7
|
+
self.keys = keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def write_to_file
|
11
|
+
current_texts = File.exists?(file_path) ? file.read : {}
|
12
|
+
current_texts.merge!(from_texts)
|
13
|
+
file.write(current_texts)
|
14
|
+
end
|
15
|
+
|
16
|
+
def read
|
17
|
+
file.read
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def file
|
22
|
+
@file ||= Translate::File.new(file_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
def from_texts
|
26
|
+
Translate::File.deep_stringify_keys(Translate::Keys.to_deep_hash(keys.inject({}) do |hash, key|
|
27
|
+
hash[key] = I18n.backend.send(:lookup, from_locale, key)
|
28
|
+
hash
|
29
|
+
end))
|
30
|
+
end
|
31
|
+
|
32
|
+
def file_path
|
33
|
+
File.join(Rails.root, "config", "locales", "log", "from_#{from_locale}_to_#{to_locale}.yml")
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Translate
|
2
|
+
class Routes
|
3
|
+
def self.translation_ui(map)
|
4
|
+
map.with_options(:controller => 'translate') do |t|
|
5
|
+
t.translate_list 'translate'
|
6
|
+
t.translate 'translate/translate', :action => 'translate'
|
7
|
+
t.translate_reload 'translate/reload', :action => 'reload'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Translate::Storage
|
2
|
+
attr_accessor :locale
|
3
|
+
|
4
|
+
def initialize(locale)
|
5
|
+
self.locale = locale.to_sym
|
6
|
+
end
|
7
|
+
|
8
|
+
def write_to_file
|
9
|
+
Translate::File.new(file_path).write(keys)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.file_paths(locale)
|
13
|
+
Dir.glob(File.join(root_dir, "config", "locales", "**","#{locale}.yml"))
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.root_dir
|
17
|
+
Rails.root
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def keys
|
22
|
+
{locale => I18n.backend.send(:translations)[locale]}
|
23
|
+
end
|
24
|
+
|
25
|
+
def file_path
|
26
|
+
File.join(Translate::Storage.root_dir, "config", "locales", "#{locale}.yml")
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
|
4
|
+
describe TranslateController do
|
5
|
+
describe "index" do
|
6
|
+
before(:each) do
|
7
|
+
controller.stub!(:per_page).and_return(1)
|
8
|
+
I18n.backend.stub!(:translations).and_return(i18n_translations)
|
9
|
+
I18n.backend.instance_eval { @initialized = true }
|
10
|
+
keys = mock(:keys)
|
11
|
+
keys.stub!(:i18n_keys).and_return(['vendor.foobar'])
|
12
|
+
Translate::Keys.should_receive(:new).and_return(keys)
|
13
|
+
Translate::Keys.should_receive(:files).and_return(files)
|
14
|
+
I18n.stub!(:available_locales).and_return([:en, :sv])
|
15
|
+
I18n.stub!(:default_locale).and_return(:sv)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "shows sorted paginated keys from the translate from locale and extracted keys by default" do
|
19
|
+
get_page :index
|
20
|
+
assigns(:from_locale).should == :sv
|
21
|
+
assigns(:to_locale).should == :en
|
22
|
+
assigns(:files).should == files
|
23
|
+
assigns(:keys).sort.should == ['articles.new.page_title', 'home.page_title', 'vendor.foobar']
|
24
|
+
assigns(:paginated_keys).should == ['articles.new.page_title']
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can be paginated with the page param" do
|
28
|
+
get_page :index, :page => 2
|
29
|
+
assigns(:files).should == files
|
30
|
+
assigns(:paginated_keys).should == ['home.page_title']
|
31
|
+
end
|
32
|
+
|
33
|
+
it "accepts a key_pattern param with key_type=starts_with" do
|
34
|
+
get_page :index, :key_pattern => 'articles', :key_type => 'starts_with'
|
35
|
+
assigns(:files).should == files
|
36
|
+
assigns(:paginated_keys).should == ['articles.new.page_title']
|
37
|
+
assigns(:total_entries).should == 1
|
38
|
+
end
|
39
|
+
|
40
|
+
it "accepts a key_pattern param with key_type=contains" do
|
41
|
+
get_page :index, :key_pattern => 'page_', :key_type => 'contains'
|
42
|
+
assigns(:files).should == files
|
43
|
+
assigns(:total_entries).should == 2
|
44
|
+
assigns(:paginated_keys).should == ['articles.new.page_title']
|
45
|
+
end
|
46
|
+
|
47
|
+
it "accepts a filter=untranslated param" do
|
48
|
+
get_page :index, :filter => 'untranslated'
|
49
|
+
assigns(:total_entries).should == 2
|
50
|
+
assigns(:paginated_keys).should == ['articles.new.page_title']
|
51
|
+
end
|
52
|
+
|
53
|
+
it "accepts a filter=translated param" do
|
54
|
+
get_page :index, :filter => 'translated'
|
55
|
+
assigns(:total_entries).should == 1
|
56
|
+
assigns(:paginated_keys).should == ['vendor.foobar']
|
57
|
+
end
|
58
|
+
|
59
|
+
it "accepts a filter=changed param" do
|
60
|
+
log = mock(:log)
|
61
|
+
old_translations = {:home => {:page_title => "Skapar ny artikel"}}
|
62
|
+
log.should_receive(:read).and_return(Translate::File.deep_stringify_keys(old_translations))
|
63
|
+
Translate::Log.should_receive(:new).with(:sv, :en, {}).and_return(log)
|
64
|
+
get_page :index, :filter => 'changed'
|
65
|
+
assigns(:total_entries).should == 1
|
66
|
+
assigns(:keys).should == ["home.page_title"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def i18n_translations
|
70
|
+
HashWithIndifferentAccess.new({
|
71
|
+
:en => {
|
72
|
+
:vendor => {
|
73
|
+
:foobar => "Foo Baar"
|
74
|
+
}
|
75
|
+
},
|
76
|
+
:sv => {
|
77
|
+
:articles => {
|
78
|
+
:new => {
|
79
|
+
:page_title => "Skapa ny artikel"
|
80
|
+
}
|
81
|
+
},
|
82
|
+
:home => {
|
83
|
+
:page_title => "Välkommen till I18n"
|
84
|
+
},
|
85
|
+
:vendor => {
|
86
|
+
:foobar => "Fobar"
|
87
|
+
}
|
88
|
+
}
|
89
|
+
})
|
90
|
+
end
|
91
|
+
|
92
|
+
def files
|
93
|
+
HashWithIndifferentAccess.new({
|
94
|
+
:'home.page_title' => ["app/views/home/index.rhtml"],
|
95
|
+
:'general.back' => ["app/views/articles/new.rhtml", "app/views/categories/new.rhtml"],
|
96
|
+
:'articles.new.page_title' => ["app/views/articles/new.rhtml"]
|
97
|
+
})
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "translate" do
|
102
|
+
it "should store translations to I18n backend and then write them to a YAML file" do
|
103
|
+
session[:from_locale] = :sv
|
104
|
+
session[:to_locale] = :en
|
105
|
+
translations = {
|
106
|
+
:articles => {
|
107
|
+
:new => {
|
108
|
+
:title => "New Article"
|
109
|
+
}
|
110
|
+
},
|
111
|
+
:category => "Category"
|
112
|
+
}
|
113
|
+
key_param = {'articles.new.title' => "New Article", "category" => "Category"}
|
114
|
+
I18n.backend.should_receive(:store_translations).with(:en, translations)
|
115
|
+
storage = mock(:storage)
|
116
|
+
storage.should_receive(:write_to_file)
|
117
|
+
Translate::Storage.should_receive(:new).with(:en).and_return(storage)
|
118
|
+
log = mock(:log)
|
119
|
+
log.should_receive(:write_to_file)
|
120
|
+
Translate::Log.should_receive(:new).with(:sv, :en, key_param.keys).and_return(log)
|
121
|
+
post :translate, "key" => key_param
|
122
|
+
response.should be_redirect
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_page(*args)
|
127
|
+
get(*args)
|
128
|
+
response.should be_success
|
129
|
+
end
|
130
|
+
end
|
data/spec/file_spec.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
3
|
+
|
4
|
+
describe Translate::File do
|
5
|
+
describe "write" do
|
6
|
+
before(:each) do
|
7
|
+
@file = Translate::File.new(file_path)
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:each) do
|
11
|
+
FileUtils.rm(file_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "writes all I18n messages for a locale to YAML file" do
|
15
|
+
@file.write(translations)
|
16
|
+
@file.read.should == Translate::File.deep_stringify_keys(translations)
|
17
|
+
end
|
18
|
+
|
19
|
+
def translations
|
20
|
+
{
|
21
|
+
:en => {
|
22
|
+
:article => {
|
23
|
+
:title => "One Article"
|
24
|
+
},
|
25
|
+
:category => "Category"
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "deep_stringify_keys" do
|
32
|
+
it "should convert all keys in a hash to strings" do
|
33
|
+
Translate::File.deep_stringify_keys({
|
34
|
+
:en => {
|
35
|
+
:article => {
|
36
|
+
:title => "One Article"
|
37
|
+
},
|
38
|
+
:category => "Category"
|
39
|
+
}
|
40
|
+
}).should == {
|
41
|
+
"en" => {
|
42
|
+
"article" => {
|
43
|
+
"title" => "One Article"
|
44
|
+
},
|
45
|
+
"category" => "Category"
|
46
|
+
}
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def file_path
|
52
|
+
File.join(File.dirname(__FILE__), "files", "en.yml")
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Article < ActiveRecord::Base
|
2
|
+
def validate
|
3
|
+
# t('li')
|
4
|
+
errors.add_to_base([t(:'article.key1') + "#{t('article.key2')}"])
|
5
|
+
I18n.t 'article.key3'
|
6
|
+
I18n.t 'article.key3'
|
7
|
+
I18n.t :'article.key4'
|
8
|
+
I18n.translate :'article.key5'
|
9
|
+
'bla bla t' + "blubba bla" + ' foobar'
|
10
|
+
'bla bla t ' + "blubba bla" + ' foobar'
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= t(:'category_erb.key1') %>
|
@@ -0,0 +1 @@
|
|
1
|
+
t(:'category_html.key1')
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= t(:'category_html_erb.key1') %>
|
@@ -0,0 +1 @@
|
|
1
|
+
I18n.t('js.alert')
|
data/spec/keys_spec.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe Translate::Keys do
|
5
|
+
before(:each) do
|
6
|
+
I18n.stub!(:default_locale).and_return(:en)
|
7
|
+
@keys = Translate::Keys.new
|
8
|
+
Translate::Storage.stub!(:root_dir).and_return(i18n_files_dir)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "to_a" do
|
12
|
+
it "extracts keys from I18n lookups in .rb, .html.erb, and .rhtml files" do
|
13
|
+
@keys.to_a.map(&:to_s).sort.should == ['article.key1', 'article.key2', 'article.key3', 'article.key4', 'article.key5',
|
14
|
+
'category_erb.key1', 'category_html_erb.key1', 'category_rhtml.key1', 'js.alert']
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "to_hash" do
|
19
|
+
it "return a hash with I18n keys and file lists" do
|
20
|
+
@keys.to_hash[:'article.key3'].should == ["vendor/plugins/translate/spec/files/translate/app/models/article.rb"]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "i18n_keys" do
|
25
|
+
before(:each) do
|
26
|
+
I18n.backend.send(:init_translations) unless I18n.backend.initialized?
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return all keys in the I18n backend translations hash" do
|
30
|
+
I18n.backend.should_receive(:translations).and_return(translations)
|
31
|
+
@keys.i18n_keys(:en).should == ['articles.new.page_title', 'categories.flash.created', 'empty', 'home.about']
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "untranslated_keys" do
|
35
|
+
before(:each) do
|
36
|
+
I18n.backend.stub!(:translations).and_return(translations)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should return a hash with keys with missing translations in each locale" do
|
40
|
+
@keys.untranslated_keys.should == {
|
41
|
+
:sv => ['articles.new.page_title', 'categories.flash.created', 'empty']
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "missing_keys" do
|
47
|
+
before(:each) do
|
48
|
+
@file_path = File.join(i18n_files_dir, "config", "locales", "en.yml")
|
49
|
+
Translate::File.new(@file_path).write({
|
50
|
+
:en => {
|
51
|
+
:home => {
|
52
|
+
:page_title => false,
|
53
|
+
:intro => {
|
54
|
+
:one => "intro one",
|
55
|
+
:other => "intro other"
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
})
|
60
|
+
end
|
61
|
+
|
62
|
+
after(:each) do
|
63
|
+
FileUtils.rm(@file_path)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should return a hash with keys that are not in the locale file" do
|
67
|
+
@keys.stub!(:files).and_return({
|
68
|
+
:'home.page_title' => "app/views/home/index.rhtml",
|
69
|
+
:'home.intro' => 'app/views/home/index.rhtml',
|
70
|
+
:'home.signup' => "app/views/home/_signup.rhtml",
|
71
|
+
:'about.index.page_title' => "app/views/about/index.rhtml"
|
72
|
+
})
|
73
|
+
@keys.missing_keys.should == {
|
74
|
+
:'home.signup' => "app/views/home/_signup.rhtml",
|
75
|
+
:'about.index.page_title' => "app/views/about/index.rhtml"
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "contains_key?" do
|
81
|
+
it "works" do
|
82
|
+
hash = {
|
83
|
+
:foo => {
|
84
|
+
:bar => {
|
85
|
+
:baz => false
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
Translate::Keys.contains_key?(hash, "").should be_false
|
90
|
+
Translate::Keys.contains_key?(hash, "foo").should be_true
|
91
|
+
Translate::Keys.contains_key?(hash, "foo.bar").should be_true
|
92
|
+
Translate::Keys.contains_key?(hash, "foo.bar.baz").should be_true
|
93
|
+
Translate::Keys.contains_key?(hash, :"foo.bar.baz").should be_true
|
94
|
+
Translate::Keys.contains_key?(hash, "foo.bar.baz.bla").should be_false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "translated_locales" do
|
99
|
+
before(:each) do
|
100
|
+
I18n.stub!(:default_locale).and_return(:en)
|
101
|
+
I18n.stub!(:available_locales).and_return([:sv, :no, :en, :root])
|
102
|
+
end
|
103
|
+
|
104
|
+
it "returns all avaiable except :root and the default" do
|
105
|
+
Translate::Keys.translated_locales.should == [:sv, :no]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "to_deep_hash" do
|
110
|
+
it "convert shallow hash with dot separated keys to deep hash" do
|
111
|
+
Translate::Keys.to_deep_hash(shallow_hash).should == deep_hash
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "to_shallow_hash" do
|
116
|
+
it "converts a deep hash to a shallow one" do
|
117
|
+
Translate::Keys.to_shallow_hash(deep_hash).should == shallow_hash
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
##########################################################################
|
122
|
+
#
|
123
|
+
# Helper Methods
|
124
|
+
#
|
125
|
+
##########################################################################
|
126
|
+
|
127
|
+
def translations
|
128
|
+
{
|
129
|
+
:en => {
|
130
|
+
:home => {
|
131
|
+
:about => "This site is about making money"
|
132
|
+
},
|
133
|
+
:articles => {
|
134
|
+
:new => {
|
135
|
+
:page_title => "New Article"
|
136
|
+
}
|
137
|
+
},
|
138
|
+
:categories => {
|
139
|
+
:flash => {
|
140
|
+
:created => "Category created"
|
141
|
+
}
|
142
|
+
},
|
143
|
+
:empty => nil
|
144
|
+
},
|
145
|
+
:sv => {
|
146
|
+
:home => {
|
147
|
+
:about => false
|
148
|
+
}
|
149
|
+
}
|
150
|
+
}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def shallow_hash
|
155
|
+
{
|
156
|
+
'pressrelease.label.one' => "Pressmeddelande",
|
157
|
+
'pressrelease.label.other' => "Pressmeddelanden",
|
158
|
+
'article' => "Artikel",
|
159
|
+
'category' => ''
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
def deep_hash
|
164
|
+
{
|
165
|
+
:pressrelease => {
|
166
|
+
:label => {
|
167
|
+
:one => "Pressmeddelande",
|
168
|
+
:other => "Pressmeddelanden"
|
169
|
+
}
|
170
|
+
},
|
171
|
+
:article => "Artikel",
|
172
|
+
:category => ''
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
def i18n_files_dir
|
177
|
+
File.join(ENV['PWD'], "spec", "files", "translate")
|
178
|
+
end
|
179
|
+
end
|