fast_gettext 0.4.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +2 -0
  2. data/CHANGELOG +6 -0
  3. data/README.markdown +181 -0
  4. data/Rakefile +42 -0
  5. data/VERSION +1 -0
  6. data/benchmark/base.rb +42 -0
  7. data/benchmark/baseline.rb +5 -0
  8. data/benchmark/fast_gettext.rb +18 -0
  9. data/benchmark/i18n_simple.rb +7 -0
  10. data/benchmark/ideal.rb +22 -0
  11. data/benchmark/locale/de.yml +127 -0
  12. data/benchmark/locale/de/LC_MESSAGES/large.mo +0 -0
  13. data/benchmark/misc/threadsave.rb +21 -0
  14. data/benchmark/namespace/fast_gettext.rb +15 -0
  15. data/benchmark/namespace/original.rb +14 -0
  16. data/benchmark/original.rb +15 -0
  17. data/examples/db/migration.rb +22 -0
  18. data/examples/missing_translation_logger.rb +13 -0
  19. data/fast_gettext.gemspec +114 -0
  20. data/lib/fast_gettext.rb +30 -0
  21. data/lib/fast_gettext/mo_file.rb +67 -0
  22. data/lib/fast_gettext/po_file.rb +14 -0
  23. data/lib/fast_gettext/storage.rb +188 -0
  24. data/lib/fast_gettext/translation.rb +53 -0
  25. data/lib/fast_gettext/translation_repository.rb +15 -0
  26. data/lib/fast_gettext/translation_repository/base.rb +49 -0
  27. data/lib/fast_gettext/translation_repository/chain.rb +43 -0
  28. data/lib/fast_gettext/translation_repository/db.rb +57 -0
  29. data/lib/fast_gettext/translation_repository/db_models/translation_key.rb +26 -0
  30. data/lib/fast_gettext/translation_repository/db_models/translation_text.rb +9 -0
  31. data/lib/fast_gettext/translation_repository/logger.rb +27 -0
  32. data/lib/fast_gettext/translation_repository/mo.rb +35 -0
  33. data/lib/fast_gettext/translation_repository/po.rb +18 -0
  34. data/spec/aa_unconfigued_spec.rb +21 -0
  35. data/spec/fast_gettext/mo_file_spec.rb +36 -0
  36. data/spec/fast_gettext/storage_spec.rb +309 -0
  37. data/spec/fast_gettext/translation_repository/base_spec.rb +21 -0
  38. data/spec/fast_gettext/translation_repository/chain_spec.rb +82 -0
  39. data/spec/fast_gettext/translation_repository/db_spec.rb +71 -0
  40. data/spec/fast_gettext/translation_repository/logger_spec.rb +41 -0
  41. data/spec/fast_gettext/translation_repository/mo_spec.rb +31 -0
  42. data/spec/fast_gettext/translation_repository/po_spec.rb +31 -0
  43. data/spec/fast_gettext/translation_spec.rb +152 -0
  44. data/spec/fast_gettext_spec.rb +44 -0
  45. data/spec/locale/de/LC_MESSAGES/test.mo +0 -0
  46. data/spec/locale/de/test.po +61 -0
  47. data/spec/locale/en/LC_MESSAGES/plural_test.mo +0 -0
  48. data/spec/locale/en/LC_MESSAGES/test.mo +0 -0
  49. data/spec/locale/en/plural_test.po +20 -0
  50. data/spec/locale/en/test.po +59 -0
  51. data/spec/spec_helper.rb +26 -0
  52. data/spec/vendor/fake_load_path/iconv.rb +2 -0
  53. data/spec/vendor/iconv_spec.rb +27 -0
  54. data/spec/vendor/string_spec.rb +67 -0
  55. data/vendor/README.rdoc +236 -0
  56. data/vendor/empty.mo +0 -0
  57. data/vendor/iconv.rb +107 -0
  58. data/vendor/mofile.rb +296 -0
  59. data/vendor/poparser.rb +331 -0
  60. data/vendor/string.rb +58 -0
  61. metadata +130 -0
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ pkg
2
+ benchmark/locle
data/CHANGELOG ADDED
@@ -0,0 +1,6 @@
1
+ 0.4.14 -- "" is translated as "", not as gettext meta information
2
+ 0.4.0 -- pluralisation_rules is no longer stored in each repository, only retrived. Added Chain and Logger repository.
3
+ 0.3.6 -- FastGettext.default_locale=
4
+ 0.3.5 -- FastGettext.default_text_domain=
5
+ 0.3.4 -- Exceptions are thrown, not returned when translating without text domain
6
+ 0.3 -- pluralisation methods accept/return n plural forms, contrary to singular/plural before
data/README.markdown ADDED
@@ -0,0 +1,181 @@
1
+ FastGettext
2
+ ===========
3
+ GetText but 3.5 x faster, 560 x less memory, simple, clean namespace (7 vs 34) and threadsave!
4
+
5
+ It supports multiple backends (atm: .mo files, .po files, Database(ActiveRecor + any other), Chain, Loggers) and can easily be extended.
6
+
7
+ [Example Rails application](https://github.com/grosser/gettext_i18n_rails_example)
8
+
9
+
10
+ Setup
11
+ =====
12
+ ### 1. Install
13
+ sudo gem install fast_gettext
14
+
15
+ ### 2. Add a translation repository
16
+
17
+ From mo files (traditional/default)
18
+ FastGettext.add_text_domain('my_app',:path=>'locale')
19
+
20
+ Or po files (less maintenacnce than mo)
21
+ FastGettext.add_text_domain('my_app',:path=>'locale', :type=>:po)
22
+
23
+ Or database (scaleable, great for many locales/translators)
24
+ require "fast_gettext/translation_repository/db"
25
+ include FastGettext::TranslationRepository::Db.require_models #load and include default models
26
+ FastGettext.add_text_domain('my_app', :type=>:db, :model=>TranslationKey)
27
+
28
+ ### 3. Choose text domain and locale for translation
29
+ Do this once in every Thread. (e.g. Rails -> ApplicationController)
30
+ FastGettext.text_domain = 'my_app'
31
+ FastGettext.available_locales = ['de','en','fr','en_US','en_UK'] # only allow these locales to be set (optional)
32
+ FastGettext.locale = 'de'
33
+
34
+ ### 4. Start translating
35
+ include FastGettext::Translation
36
+ _('Car') == 'Auto'
37
+ _('not-found') == 'not-found'
38
+ s_('Namespace|no-found') == 'not-found'
39
+ n_('Axis','Axis',3) == 'Achsen' #German plural of Axis
40
+
41
+
42
+ Managing translations
43
+ ============
44
+ ### mo/po-files
45
+ Generate .po or .mo files using GetText parser (example tasks at [gettext_i18n_rails](http://github.com/grosser/gettext_i18n_rails))
46
+
47
+ Tell Gettext where your .mo or .po files lie, e.g. for locale/de/my_app.po and locale/de/LC_MESSAGES/my_app.mo
48
+ FastGettext.add_text_domain('my_app',:path=>'locale')
49
+
50
+ Use the [original GetText](http://github.com/mutoh/gettext) to create and manage po/mo-files.
51
+ (Work on a po/mo parser & reader that is easier to use has started, contributions welcome @ [pomo](http://github.com/grosser/pomo) )
52
+
53
+ ###Database
54
+ !!!new/only short time in production, please report back any ideas/suggestions you have!!!
55
+ [Example migration for ActiveRecord](http://github.com/grosser/fast_gettext/blob/master/examples/db/migration.rb)
56
+ The default plural seperator is `||||` but you may overwrite it (or suggest a better one..).
57
+
58
+ This is usable with any model DataMapper/Sequel or any other(non-database) backend, the only thing you need to do is respond to the self.translation(key, locale) call.
59
+ If you want to use your own models, have a look at the [default models](http://github.com/grosser/fast_gettext/tree/master/lib/fast_gettext/translation_repository/db_models) to see what you want/need to implement.
60
+
61
+ To manage translations via a Web GUI, use a [Rails application and the translation_db_engine](http://github.com/grosser/translation_db_engine)
62
+
63
+
64
+ Performance
65
+ ===========
66
+ 50_000 translations speed / memory
67
+ small translation file <-> large translation file
68
+ (ruby enterprise 1.8.6, your results may vary, try `rake benchmark`)
69
+ Baseline: (doing nothing in a loop)
70
+ 0.250000s / 0K <->
71
+
72
+ Ideal: (primitive Hash lookup)
73
+ 0.820000s / 4K <-> 0.760000s / 4K
74
+
75
+ FastGettext:
76
+ 1.360000s / 8K <-> 1.320000s / 8K
77
+
78
+ GetText 2.0.1:
79
+ 4.880000s / 4480K <-> 4.810000s / 4480K
80
+
81
+ ActiveSupport I18n::Backend::Simple :
82
+ 21.770000s / 10100K <->
83
+
84
+
85
+ Rails
86
+ =======================
87
+ Try the [gettext_i18n_rails plugin](http://github.com/grosser/gettext_i18n_rails), it simplifies the setup.
88
+ Try the [translation_db_engine](http://github.com/grosser/translation_db_engine), to manage your translations in a db.
89
+
90
+ Setting `available_locales`,`text_domain` or `locale` will not work inside the `evironment.rb`,
91
+ since it runs in a different thread then e.g. controllers, so set them inside your application_controller.
92
+
93
+ #environment.rb after initializers
94
+ Object.send(:include,FastGettext::Translation)
95
+ FastGettext.add_text_domain('accounting',:path=>'locale')
96
+ FastGettext.add_text_domain('frontend',:path=>'locale')
97
+ ...
98
+
99
+ #application_controller.rb
100
+ class ApplicationController ...
101
+ include FastGettext::Translation
102
+ before_filter :set_locale
103
+ def set_locale
104
+ FastGettext.available_locales = ['de','en',...]
105
+ FastGettext.text_domain = 'frontend'
106
+ session[:locale] = I18n.locale = FastGettext.set_locale(params[:locale] || session[:locale] || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en')
107
+ end
108
+
109
+
110
+ Advanced features
111
+ =================
112
+ ###Abnormal pluralisation
113
+ Pluralisation rules can be set directly via a lambda (see specs/), or by using the Gettext
114
+ plural definition (see spec/locale/en/test_plural.po or [Plural expressions for all languages](http://translate.sourceforge.net/wiki/l10n/pluralforms).
115
+
116
+
117
+ ###default_text_domain
118
+ If you only use one text domain, setting `FastGettext.default_text_domain = 'app'`
119
+ is sufficient and no more `text_domain=` is needed
120
+
121
+ ###default_locale
122
+ If the simple rule of "first `availble_locale` or 'en'" is not suficcient for you, set `FastGettext.default_locale = 'de'`.
123
+
124
+ ###default_available_locales
125
+ Fallback when no available_locales are set
126
+
127
+ ###Chains
128
+ You can use any number of repositories to find a translation. Simply add them to a chain and when
129
+ the first cannot translate a given key, the next is asked and so forth.
130
+ repos = [
131
+ FastGettext::TranslationRepository.build('new', :path=>'....'),
132
+ FastGettext::TranslationRepository.build('old', :path=>'....')
133
+ ]
134
+ FastGettext.add_text_domain 'combined', :type=>:chain, :chain=>repos
135
+
136
+ ###Logger
137
+ When you want to know which keys could not be translated or were used, add a Logger to a Chain:
138
+ repos = [
139
+ FastGettext::TranslationRepository.build('app', :path=>'....')
140
+ FastGettext::TranslationRepository.build('logger', :type=>:logger, :callback=>lamda{|key_or_array_of_ids| ... }),
141
+ }
142
+ FastGettext.add_text_domain 'combined', :type=>:chain, :chain=>repos
143
+ If the Logger is in position #1 it will see all translations, if it is in position #2 it will only see the unfound.
144
+ Unfound may not always mean missing, if you chose not to translate a word because the key is a good translation, it will appear nevertheless.
145
+ A lambda or anything that responds to `call` will do as callback. A good starting point may be `examples/missing_translations_logger.rb`.
146
+
147
+ ###Plugins
148
+ Want a yml, xml version ?
149
+ Write your own TranslationRepository!
150
+ #fast_gettext/translation_repository/xxx.rb
151
+ module FastGettext
152
+ module TranslationRepository
153
+ class Wtf
154
+ define initialize(name,options), [key], plural(*keys) and
155
+ either inherit from TranslationRepository::Base or define available_locales and pluralisation_rule
156
+ end
157
+ end
158
+ end
159
+
160
+
161
+ FAQ
162
+ ===
163
+ - [Problems with ActiveRecord messages?](http://wiki.github.com/grosser/fast_gettext/activerecord)
164
+
165
+
166
+ TODO
167
+ ====
168
+ - YML backend that reads ActiveSupport::I18n files
169
+ - any ideas ? :D
170
+
171
+ Author
172
+ ======
173
+ Mo/Po-file parsing from Masao Mutoh, see vendor/README
174
+
175
+ ###Contributors
176
+ - [geekq](http://www.innoq.com/blog/vd)
177
+ - Rudolf Gavlas
178
+
179
+ [Michael Grosser](http://pragmatig.wordpress.com)
180
+ grosser.michael@gmail.com
181
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ desc "Run all specs in spec directory"
2
+ task :default do |t|
3
+ options = "--colour --format progress --loadby --reverse"
4
+ files = FileList['spec/**/*_spec.rb']
5
+ system("spec #{options} #{files}")
6
+ end
7
+
8
+ task :benchmark do
9
+ puts "Running on #{RUBY}"
10
+ %w[baseline ideal fast_gettext original i18n_simple].each do |bench|
11
+ puts `ruby benchmark/#{bench}.rb`
12
+ puts ""
13
+ end
14
+ end
15
+
16
+ task :namespaces do
17
+ puts `ruby benchmark/namespace/original.rb`
18
+ puts `ruby benchmark/namespace/fast_gettext.rb`
19
+ end
20
+
21
+ begin
22
+ require 'jeweler'
23
+ project_name = 'fast_gettext'
24
+ Jeweler::Tasks.new do |gem|
25
+ gem.name = project_name
26
+ gem.summary = "A simple, fast and threadsafe implementation of GetText"
27
+ gem.email = "grosser.michael@gmail.com"
28
+ gem.homepage = "http://github.com/grosser/#{project_name}"
29
+ gem.authors = ["Michael Grosser"]
30
+ gem.rubyforge_project = project_name.sub('_','-')
31
+ end
32
+
33
+ # fake task so that rubyforge:release works
34
+ task :rdoc do
35
+ `mkdir rdoc`
36
+ `echo documentation is at http://github.com/grosser/#{project_name} > rdoc/README.rdoc`
37
+ end
38
+
39
+ Jeweler::RubyforgeTasks.new
40
+ rescue LoadError
41
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
42
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.4.16
data/benchmark/base.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+ require 'benchmark'
3
+
4
+ RUNS = 50_0000
5
+ DEFAULTS = {:memory=>0}
6
+
7
+ def locale_folder(domain)
8
+ path = case domain
9
+ when 'test' then File.join(File.expand_path(File.dirname(__FILE__)),'..','spec','locale')
10
+ when 'large' then File.join(File.expand_path(File.dirname(__FILE__)),'locale')
11
+ end
12
+
13
+ mo = File.join(path,'de','LC_MESSAGES',"#{domain}.mo")
14
+ raise unless File.exist?(mo)
15
+ path
16
+ end
17
+
18
+ def results_test(&block)
19
+ print "#{(result(&block)).to_s.strip.split(' ').first}s / #{memory}K <-> "
20
+ end
21
+
22
+ def results_large
23
+ print "#{(result {_('login') == 'anmelden'}).to_s.strip.split(' ').first}s / #{memory}K"
24
+ puts ""
25
+ end
26
+
27
+ def result
28
+ result =Benchmark.measure do
29
+ RUNS.times do
30
+ raise "not translated" unless yield
31
+ end
32
+ end
33
+ result
34
+ end
35
+
36
+ def memory
37
+ pid = Process.pid
38
+ map = `pmap -d #{pid}`
39
+ map.split("\n").last.strip.squeeze(' ').split(' ')[3].to_i - DEFAULTS[:memory]
40
+ end
41
+
42
+ DEFAULTS[:memory] = memory + 4 #4 => 0 for base calls
@@ -0,0 +1,5 @@
1
+ require 'benchmark/base'
2
+
3
+ puts "Baseline: (doing nothing in a loop)"
4
+ results_test{true}
5
+ puts ""
@@ -0,0 +1,18 @@
1
+ require 'benchmark/base'
2
+
3
+ $LOAD_PATH.unshift 'lib'
4
+ require 'fast_gettext'
5
+ include FastGettext::Translation
6
+
7
+ FastGettext.available_locales = ['de','en']
8
+ FastGettext.locale = 'de'
9
+
10
+ puts "FastGettext:"
11
+ FastGettext.add_text_domain('test',:path=>locale_folder('test'))
12
+ FastGettext.text_domain = 'test'
13
+ results_test{_('car') == 'Auto'}
14
+
15
+ #i cannot add the large file, since its an internal applications mo file
16
+ FastGettext.add_text_domain('large',:path=>locale_folder('large'))
17
+ FastGettext.text_domain = 'large'
18
+ results_large
@@ -0,0 +1,7 @@
1
+ require 'benchmark/base'
2
+ require 'activesupport'
3
+ I18n.backend = I18n::Backend::Simple.new
4
+ I18n.load_path = ['benchmark/locale/de.yml']
5
+ I18n.locale = :de
6
+ puts "ActiveSupport I18n::Backend::Simple :"
7
+ results_test{I18n.translate('activerecord.models.car')=='Auto'}
@@ -0,0 +1,22 @@
1
+ require 'benchmark/base'
2
+
3
+ module FastestGettext
4
+ def set_domain(folder,domain,locale)
5
+ @data = {}
6
+ require File.join(File.dirname(__FILE__),'..','vendor','mofile')
7
+ FastGettext::GetText::MOFile.open(File.join(folder,locale,'LC_MESSAGES',"#{domain}.mo"), "UTF-8").each{|k,v|@data[k]=v}
8
+ end
9
+ def _(word)
10
+ @data[word]
11
+ end
12
+ end
13
+
14
+
15
+ include FastestGettext
16
+ set_domain(locale_folder('test'),'test','de')
17
+ puts "Ideal: (primitive Hash lookup)"
18
+ results_test{_('car') == 'Auto'}
19
+
20
+ #i cannot add the large file, since its an internal applications mo file
21
+ set_domain(locale_folder('large'),'large','de')
22
+ results_large
@@ -0,0 +1,127 @@
1
+ # German translations for Ruby on Rails
2
+ # by Clemens Kofler (clemens@railway.at)
3
+
4
+ de:
5
+ date:
6
+ formats:
7
+ default: "%d.%m.%Y"
8
+ short: "%e. %b"
9
+ long: "%e. %B %Y"
10
+ only_day: "%e"
11
+
12
+ day_names: [Sonntag, Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag]
13
+ abbr_day_names: [So, Mo, Di, Mi, Do, Fr, Sa]
14
+ month_names: [~, Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember]
15
+ abbr_month_names: [~, Jan, Feb, Mär, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez]
16
+ order: [ :day, :month, :year ]
17
+
18
+ time:
19
+ formats:
20
+ default: "%A, %e. %B %Y, %H:%M Uhr"
21
+ short: "%e. %B, %H:%M Uhr"
22
+ long: "%A, %e. %B %Y, %H:%M Uhr"
23
+ time: "%H:%M"
24
+
25
+ am: "vormittags"
26
+ pm: "nachmittags"
27
+
28
+ datetime:
29
+ distance_in_words:
30
+ half_a_minute: 'eine halbe Minute'
31
+ less_than_x_seconds:
32
+ zero: 'weniger als 1 Sekunde'
33
+ one: 'weniger als 1 Sekunde'
34
+ other: 'weniger als {{count}} Sekunden'
35
+ x_seconds:
36
+ one: '1 Sekunde'
37
+ other: '{{count}} Sekunden'
38
+ less_than_x_minutes:
39
+ zero: 'weniger als 1 Minute'
40
+ one: 'weniger als eine Minute'
41
+ other: 'weniger als {{count}} Minuten'
42
+ x_minutes:
43
+ one: '1 Minute'
44
+ other: '{{count}} Minuten'
45
+ about_x_hours:
46
+ one: 'etwa 1 Stunde'
47
+ other: 'etwa {{count}} Stunden'
48
+ x_days:
49
+ one: '1 Tag'
50
+ other: '{{count}} Tage'
51
+ about_x_months:
52
+ one: 'etwa 1 Monat'
53
+ other: 'etwa {{count}} Monate'
54
+ x_months:
55
+ one: '1 Monat'
56
+ other: '{{count}} Monate'
57
+ about_x_years:
58
+ one: 'etwa 1 Jahr'
59
+ other: 'etwa {{count}} Jahre'
60
+ over_x_years:
61
+ one: 'mehr als 1 Jahr'
62
+ other: 'mehr als {{count}} Jahre'
63
+
64
+ number:
65
+ format:
66
+ precision: 2
67
+ separator: ','
68
+ delimiter: '.'
69
+ currency:
70
+ format:
71
+ unit: '€'
72
+ format: '%n%u'
73
+ separator:
74
+ delimiter:
75
+ precision:
76
+ percentage:
77
+ format:
78
+ delimiter: ""
79
+ precision:
80
+ format:
81
+ delimiter: ""
82
+ human:
83
+ format:
84
+ delimiter: ""
85
+ precision: 1
86
+
87
+ support:
88
+ array:
89
+ sentence_connector: "und"
90
+ skip_last_comma: true
91
+
92
+ activerecord:
93
+ errors:
94
+ template:
95
+ header:
96
+ one: "Konnte dieses {{model}} Objekt nicht speichern: 1 Fehler."
97
+ other: "Konnte dieses {{model}} Objekt nicht speichern: {{count}} Fehler."
98
+ body: "Bitte überprüfen Sie die folgenden Felder:"
99
+ format:
100
+ seperator: ' '
101
+ messages:
102
+ inclusion: "ist kein gültiger Wert"
103
+ exclusion: "ist nicht verfügbar"
104
+ invalid: "ist nicht gültig"
105
+ confirmation: "stimmt nicht mit der Bestätigung überein"
106
+ accepted: "muss akzeptiert werden"
107
+ empty: "muss ausgefüllt werden"
108
+ blank: "muss ausgefüllt werden"
109
+ too_long: "ist zu lang (nicht mehr als {{count}} Zeichen)"
110
+ too_short: "ist zu kurz (nicht weniger als {{count}} Zeichen)"
111
+ wrong_length: "hat die falsche Länge (muss genau {{count}} Zeichen haben)"
112
+ taken: "ist bereits vergeben"
113
+ not_a_number: "ist keine Zahl"
114
+ greater_than: "muss größer als {{count}} sein"
115
+ greater_than_or_equal_to: "muss größer oder gleich {{count}} sein"
116
+ equal_to: "muss genau {{count}} sein"
117
+ less_than: "muss kleiner als {{count}} sein"
118
+ less_than_or_equal_to: "muss kleiner oder gleich {{count}} sein"
119
+ odd: "muss ungerade sein"
120
+ even: "muss gerade sein"
121
+ models:
122
+ car: 'BAUTO'
123
+ cars: 'CAUTO'
124
+ Car: 'DAUTO'
125
+
126
+ models:
127
+ car: 'Auto'