tolk 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2004-2010 David Heinemeier Hansson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,60 @@
1
+ Tolk is a Rails 3 engine designed to facilitate the translators doing the dirty work of translating your application to other languages.
2
+
3
+ * Installation & Setup
4
+
5
+ To install add the following to your Gemfile:
6
+
7
+ gem 'tolk'
8
+
9
+ To setup just run:
10
+
11
+ $ rake tolk:setup
12
+
13
+ * Usage
14
+
15
+ Tolk treats I18n.default_locale as the master source of strings to be translated. If you want the master source to be different from I18n.default_locale, you can override it by setting Tolk::Locale.primary_locale_name. Developers are expected to make all the changes to the master locale file ( en.yml by default ) and treat all the other locale.yml files as readonly files.
16
+
17
+ As tolk stores all the keys and translated strings in the database, you need to ask Tolk to update it's database from the primary yml file :
18
+
19
+ $ rake tolk:sync
20
+
21
+ The above will fetch all the new keys from en.yml and put them in the database. Additionally, it'll also get rid of the deleted keys from the database and reflect updated translations - if any.
22
+
23
+ If you already have data in your non primary locale files, you will need to import those to Tolk as a one time thing :
24
+
25
+ $ rake tolk:import
26
+
27
+ Upon visiting http://your_app.com/tolk - you will be presented with different options like creating new locale or providing translations for the existing locales. Once done with translating all the pending strings, you are can write back the new locales to filesystem :
28
+
29
+ $ rake tolk:dump_all
30
+
31
+ This will generate yml files for all non primary locales and put them in #{Rails.root}/config/locales/ directory by default.
32
+
33
+ You can use the dump_all method defined in Tolk::Locale directly and pass directory path as the argument if you want the generated files to be at a different location :
34
+
35
+ $ script/runner "Tolk::Locale.dump_all('/Users/lifo')"
36
+
37
+ You can even download the yml file using Tolk web interface by appending '.yml' to the locale url. E.g http://your_app.com/tolk/locales/de.yml
38
+
39
+ * Authentication
40
+
41
+ If you want to authenticate users who can access Tolk, you need to provide <tt>Tolk::ApplicationController.authenticator</tt> proc. For example :
42
+
43
+ # config/initializers/tolk.rb
44
+ Tolk::ApplicationController.authenticator = proc {
45
+ authenticate_or_request_with_http_basic do |user_name, password|
46
+ user_name == 'translator' && password == 'transpass'
47
+ end
48
+ }
49
+
50
+ Authenticator proc will be run from a before filter in controller context.
51
+
52
+ * Handling blank and non-string values
53
+
54
+ Tolk speaks YAML for non strings values. If you want to enter a nil values, you could just enter '~'. Similarly, for an Array value, you could enter :
55
+
56
+ ---
57
+ - Sun
58
+ - Mon
59
+
60
+ And Tolk will take care of generating the appropriate entry in the YAML file.
@@ -0,0 +1,17 @@
1
+ module Tolk
2
+ class ApplicationController < ActionController::Base
3
+ helper :all
4
+ protect_from_forgery
5
+
6
+ cattr_accessor :authenticator
7
+ before_filter :authenticate
8
+
9
+ def authenticate
10
+ self.authenticator.bind(self).call if self.authenticator && self.authenticator.respond_to?(:call)
11
+ end
12
+
13
+ def ensure_no_primary_locale
14
+ redirect_to tolk_locales_path if @locale.primary?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ module Tolk
2
+ class LocalesController < Tolk::ApplicationController
3
+ before_filter :find_locale, :only => [:show, :all, :update, :updated]
4
+ before_filter :ensure_no_primary_locale, :only => [:all, :update, :show, :updated]
5
+
6
+ def index
7
+ @locales = Tolk::Locale.secondary_locales
8
+ end
9
+
10
+ def show
11
+ respond_to do |format|
12
+ format.html do
13
+ @phrases = @locale.phrases_without_translation(params[:page])
14
+ end
15
+ format.atom { @phrases = @locale.phrases_without_translation(params[:page], :per_page => 50) }
16
+ format.yaml { render :text => @locale.to_hash.ya2yaml(:syck_compatible => true) }
17
+ end
18
+ end
19
+
20
+ def update
21
+ @locale.translations_attributes = params[:translations]
22
+ @locale.save
23
+ redirect_to request.referrer
24
+ end
25
+
26
+ def all
27
+ @phrases = @locale.phrases_with_translation(params[:page])
28
+ end
29
+
30
+ def updated
31
+ @phrases = @locale.phrases_with_updated_translation(params[:page])
32
+ render :all
33
+ end
34
+
35
+ def create
36
+ Tolk::Locale.create!(params[:tolk_locale])
37
+ redirect_to :action => :index
38
+ end
39
+
40
+ private
41
+
42
+ def find_locale
43
+ @locale = Tolk::Locale.find_by_name!(params[:id])
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,15 @@
1
+ module Tolk
2
+ class SearchesController < Tolk::ApplicationController
3
+ before_filter :find_locale
4
+
5
+ def show
6
+ @phrases = @locale.search_phrases(params[:q], params[:scope].to_sym, params[:page])
7
+ end
8
+
9
+ private
10
+
11
+ def find_locale
12
+ @locale = Tolk::Locale.find_by_name!(params[:locale])
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ module Tolk
2
+ module ApplicationHelper
3
+ def format_i18n_value(value)
4
+ h(yaml_value(value)).gsub(/\n/, '<br />')
5
+ end
6
+
7
+ def format_i18n_text_area_value(value)
8
+ yaml_value(value)
9
+ end
10
+
11
+ def yaml_value(value)
12
+ if value.present?
13
+ unless value.is_a?(String)
14
+ value = value.respond_to?(:ya2yaml) ? value.ya2yaml(:syck_compatible => true) : value.to_yaml
15
+ end
16
+ end
17
+
18
+ value
19
+ end
20
+
21
+ def tolk_locale_selection
22
+ existing_locale_names = Tolk::Locale.all.map(&:name)
23
+
24
+ pairs = Tolk::Locale::MAPPING.to_a.map(&:reverse).sort_by(&:first)
25
+ pairs.reject {|pair| existing_locale_names.include?(pair.last) }
26
+ end
27
+
28
+ def scope_selector_for(locale)
29
+ select_tag 'scope', options_for_select([[Tolk::Locale.primary_locale.language_name, "origin"],
30
+ [locale.language_name, "target"]], params[:scope])
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,246 @@
1
+ module Tolk
2
+ class Locale < ActiveRecord::Base
3
+ set_table_name "tolk_locales"
4
+
5
+ MAPPING = {
6
+ 'ar' => 'Arabic',
7
+ 'bs' => 'Bosnian',
8
+ 'bt' => 'Bulgarian',
9
+ 'ca' => 'Catalan',
10
+ 'cz' => 'Czech',
11
+ 'da' => 'Danish',
12
+ 'de' => 'German',
13
+ 'dsb' => 'Lower Sorbian',
14
+ 'el' => 'Greek',
15
+ 'en' => 'English',
16
+ 'es' => 'Spanish',
17
+ 'et' => 'Estonian',
18
+ 'fa' => 'Persian',
19
+ 'fi' => 'Finnish',
20
+ 'fr' => 'French',
21
+ 'he' => 'Hebrew',
22
+ 'hr' => 'Croatian',
23
+ 'hsb' => 'Upper Sorbian',
24
+ 'hu' => 'Hungarian',
25
+ 'id' => 'Indonesian',
26
+ 'is' => 'Icelandic',
27
+ 'it' => 'Italian',
28
+ 'jp' => 'Japanese',
29
+ 'ko' => 'Korean',
30
+ 'lo' => 'Lao',
31
+ 'lt' => 'Lithuanian',
32
+ 'lv' => 'Latvian',
33
+ 'mk' => 'Macedonian',
34
+ 'nl' => 'Dutch',
35
+ 'no' => 'Norwegian',
36
+ 'pl' => 'Polish',
37
+ 'pt-BR' => 'Portuguese (Brazilian)',
38
+ 'pt-PT' => 'Portuguese (Portugal)',
39
+ 'ro' => 'Romanian',
40
+ 'ru' => 'Russian',
41
+ 'se' => 'Swedish',
42
+ 'sk' => 'Slovak',
43
+ 'sl' => 'Slovenian',
44
+ 'sr' => 'Serbian',
45
+ 'sw' => 'Swahili',
46
+ 'th' => 'Thai',
47
+ 'tr' => 'Turkish',
48
+ 'uk' => 'Ukrainian',
49
+ 'vi' => 'Vietnamese',
50
+ 'zh' => 'Chinese'
51
+ }
52
+
53
+ has_many :phrases, :through => :translations, :class_name => 'Tolk::Phrase'
54
+ has_many :translations, :class_name => 'Tolk::Translation', :dependent => :destroy
55
+ accepts_nested_attributes_for :translations, :reject_if => proc { |attributes| attributes['text'].blank? }
56
+ before_validation :remove_invalid_translations_from_target, :on => :update
57
+
58
+ cattr_accessor :locales_config_path
59
+ self.locales_config_path = "#{Rails.root}/config/locales"
60
+
61
+ cattr_accessor :primary_locale_name
62
+ self.primary_locale_name = I18n.default_locale.to_s
63
+
64
+ include Tolk::Sync
65
+ include Tolk::Import
66
+
67
+ validates_uniqueness_of :name
68
+ validates_presence_of :name
69
+
70
+ cattr_accessor :special_prefixes
71
+ self.special_prefixes = ['activerecord.attributes']
72
+
73
+ cattr_accessor :special_keys
74
+ self.special_keys = ['activerecord.models']
75
+
76
+ class << self
77
+ def primary_locale(reload = false)
78
+ @_primary_locale = nil if reload
79
+ @_primary_locale ||= begin
80
+ raise "Primary locale is not set. Please set Locale.primary_locale_name in your application's config file" unless self.primary_locale_name
81
+ find_or_create_by_name(self.primary_locale_name)
82
+ end
83
+ end
84
+
85
+ def primary_language_name
86
+ primary_locale.language_name
87
+ end
88
+
89
+ def secondary_locales
90
+ all - [primary_locale]
91
+ end
92
+
93
+ def dump_all(to = self.locales_config_path)
94
+ secondary_locales.each do |locale|
95
+ File.open("#{to}/#{locale.name}.yml", "w+") do |file|
96
+ data = locale.to_hash
97
+ data.respond_to?(:ya2yaml) ? file.write(data.ya2yaml(:syck_compatible => true)) : YAML.dump(locale.to_hash, file)
98
+ end
99
+ end
100
+ end
101
+
102
+ def special_key_or_prefix?(prefix, key)
103
+ self.special_prefixes.include?(prefix) || self.special_keys.include?(key)
104
+ end
105
+
106
+ PLURALIZATION_KEYS = ['zero', 'one', 'two', 'few', 'many', 'other']
107
+ def pluralization_data?(data)
108
+ keys = data.keys.map(&:to_s)
109
+ keys.all? {|k| PLURALIZATION_KEYS.include?(k) }
110
+ end
111
+ end
112
+
113
+ def has_updated_translations?
114
+ translations.count(:conditions => {:'tolk_translations.primary_updated' => true}) > 0
115
+ end
116
+
117
+ def phrases_with_translation(page = nil)
118
+ find_phrases_with_translations(page, :'tolk_translations.primary_updated' => false)
119
+ end
120
+
121
+ def phrases_with_updated_translation(page = nil)
122
+ find_phrases_with_translations(page, :'tolk_translations.primary_updated' => true)
123
+ end
124
+
125
+ def count_phrases_without_translation
126
+ existing_ids = self.translations.all(:select => 'tolk_translations.phrase_id').map(&:phrase_id).uniq
127
+ Tolk::Phrase.count - existing_ids.count
128
+ end
129
+
130
+ def phrases_without_translation(page = nil, options = {})
131
+ phrases = Tolk::Phrase.scoped(:order => 'tolk_phrases.key ASC')
132
+
133
+ existing_ids = self.translations.all(:select => 'tolk_translations.phrase_id').map(&:phrase_id).uniq
134
+ phrases = phrases.scoped(:conditions => ['tolk_phrases.id NOT IN (?)', existing_ids]) if existing_ids.present?
135
+
136
+ result = phrases.paginate({:page => page}.merge(options))
137
+ Tolk::Phrase.send :preload_associations, result, :translations
138
+ result
139
+ end
140
+
141
+ def search_phrases(query, scope, page = nil, options = {})
142
+ return [] unless query.present?
143
+
144
+ translations = case scope
145
+ when :origin
146
+ Tolk::Locale.primary_locale.translations.containing_text(query)
147
+ else # :target
148
+ self.translations.containing_text(query)
149
+ end
150
+
151
+ phrases = Tolk::Phrase.scoped(:order => 'tolk_phrases.key ASC')
152
+ phrases = phrases.scoped(:conditions => ['tolk_phrases.id IN(?)', translations.map(&:phrase_id).uniq])
153
+ phrases.paginate({:page => page}.merge(options))
154
+ end
155
+
156
+ def search_phrases_without_translation(query, page = nil, options = {})
157
+ return phrases_without_translation(page, options) unless query.present?
158
+
159
+ phrases = Tolk::Phrase.scoped(:order => 'tolk_phrases.key ASC')
160
+
161
+ found_translations_ids = Tolk::Locale.primary_locale.translations.all(:conditions => ["tolk_translations.text LIKE ?", "%#{query}%"], :select => 'tolk_translations.phrase_id').map(&:phrase_id).uniq
162
+ existing_ids = self.translations.all(:select => 'tolk_translations.phrase_id').map(&:phrase_id).uniq
163
+ phrases = phrases.scoped(:conditions => ['tolk_phrases.id NOT IN (?) AND tolk_phrases.id IN(?)', existing_ids, found_translations_ids]) if existing_ids.present?
164
+
165
+ result = phrases.paginate({:page => page}.merge(options))
166
+ Tolk::Phrase.send :preload_associations, result, :translations
167
+ result
168
+ end
169
+
170
+ def to_hash
171
+ { name => translations.each_with_object({}) do |translation, locale|
172
+ if translation.phrase.key.include?(".")
173
+ locale.deep_merge!(unsquish(translation.phrase.key, translation.value))
174
+ else
175
+ locale[translation.phrase.key] = translation.value
176
+ end
177
+ end }
178
+ end
179
+
180
+ def to_param
181
+ name.parameterize
182
+ end
183
+
184
+ def primary?
185
+ name == self.class.primary_locale_name
186
+ end
187
+
188
+ def language_name
189
+ MAPPING[self.name] || self.name
190
+ end
191
+
192
+ def [](key)
193
+ if phrase = Tolk::Phrase.find_by_key(key)
194
+ t = self.translations.find_by_phrase_id(phrase.id)
195
+ t.text if t
196
+ end
197
+ end
198
+
199
+ def translations_with_html
200
+ translations = self.translations.all(:conditions => "tolk_translations.text LIKE '%>%' AND
201
+ tolk_translations.text LIKE '%<%' AND tolk_phrases.key NOT LIKE '%_html'", :joins => :phrase)
202
+ Translation.send :preload_associations, translations, :phrase
203
+ translations
204
+ end
205
+
206
+ private
207
+
208
+ def remove_invalid_translations_from_target
209
+ self.translations.proxy_target.each do |t|
210
+ unless t.valid?
211
+ self.translations.proxy_target.delete(t)
212
+ else
213
+ t.updated_at = Time.current # Silly hax to fool autosave into saving the record
214
+ end
215
+ end
216
+
217
+ true
218
+ end
219
+
220
+ def find_phrases_with_translations(page, conditions = {})
221
+ result = Tolk::Phrase.paginate(:page => page,
222
+ :conditions => { :'tolk_translations.locale_id' => self.id }.merge(conditions),
223
+ :joins => :translations, :order => 'tolk_phrases.key ASC')
224
+
225
+ Tolk::Phrase.send :preload_associations, result, :translations
226
+
227
+ result.each do |phrase|
228
+ phrase.translation = phrase.translations.for(self)
229
+ end
230
+
231
+ result
232
+ end
233
+
234
+ def unsquish(string, value)
235
+ if string.is_a?(String)
236
+ unsquish(string.split("."), value)
237
+ elsif string.size == 1
238
+ { string.first => value }
239
+ else
240
+ key = string[0]
241
+ rest = string[1..-1]
242
+ { key => unsquish(rest, value) }
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,22 @@
1
+ module Tolk
2
+ class Phrase < ActiveRecord::Base
3
+ set_table_name "tolk_phrases"
4
+
5
+ validates_uniqueness_of :key
6
+
7
+ cattr_accessor :per_page
8
+ self.per_page = 30
9
+
10
+ has_many :translations, :class_name => 'Tolk::Translation', :dependent => :destroy do
11
+ def primary
12
+ to_a.detect {|t| t.locale_id == Tolk::Locale.primary_locale.id}
13
+ end
14
+
15
+ def for(locale)
16
+ to_a.detect {|t| t.locale_id == locale.id}
17
+ end
18
+ end
19
+
20
+ attr_accessor :translation
21
+ end
22
+ end