pax_global_header 0000666 0000000 0000000 00000000064 12607537372 0014526 g ustar 00root root 0000000 0000000 52 comment=f766a34cea81196b8ecda55ad36ea351db681dd2
fast_gettext-1.0.0/ 0000775 0000000 0000000 00000000000 12607537372 0014225 5 ustar 00root root 0000000 0000000 fast_gettext-1.0.0/.gitignore 0000664 0000000 0000000 00000000024 12607537372 0016211 0 ustar 00root root 0000000 0000000 pkg
benchmark/locle
fast_gettext-1.0.0/.travis.yml 0000664 0000000 0000000 00000000753 12607537372 0016343 0 ustar 00root root 0000000 0000000 bundler_args: ""
script: "bundle exec rake spec"
rvm:
- ree
- 1.9.3
- 2.0.0
- 2.1.2
gemfile:
- gemfiles/rails23.gemfile
- gemfiles/rails32.gemfile
- gemfiles/rails40.gemfile
- gemfiles/rails41.gemfile
bundler_args: --no-deployment
matrix:
exclude:
- rvm: 2.0.0
gemfile: gemfiles/rails23.gemfile
- rvm: 2.1.2
gemfile: gemfiles/rails23.gemfile
- rvm: ree
gemfile: gemfiles/rails40.gemfile
- rvm: ree
gemfile: gemfiles/rails41.gemfile
fast_gettext-1.0.0/CHANGELOG 0000664 0000000 0000000 00000001520 12607537372 0015435 0 ustar 00root root 0000000 0000000 1.0.0 -- do not enforce attr_accessible unless ProtectedAttributes are loaded
0.9.0 -- reworked internals of caching to be plugable
0.7.0 -- set_locale resets to default locale if none of the available locales was tried to set
0.6.0 -- plurals use singular translations as fallack e.g. you translated 'Axis' then n_('Axis','Axis',1) would return the translation for 'Axis' if no plural translation was found
0.4.14 -- "" is translated as "", not as gettext meta information
0.4.0 -- pluralisation_rules is no longer stored in each repository, only retrived. Added Chain and Logger repository.
0.3.6 -- FastGettext.default_locale=
0.3.5 -- FastGettext.default_text_domain=
0.3.4 -- Exceptions are thrown, not returned when translating without text domain
0.3 -- pluralisation methods accept/return n plural forms, contrary to singular/plural before
fast_gettext-1.0.0/Gemfile 0000664 0000000 0000000 00000000046 12607537372 0015520 0 ustar 00root root 0000000 0000000 source "https://rubygems.org"
gemspec
fast_gettext-1.0.0/Gemfile.lock 0000664 0000000 0000000 00000002236 12607537372 0016452 0 ustar 00root root 0000000 0000000 PATH
remote: .
specs:
fast_gettext (1.0.0)
GEM
remote: https://rubygems.org/
specs:
activemodel (4.1.4)
activesupport (= 4.1.4)
builder (~> 3.1)
activerecord (4.1.4)
activemodel (= 4.1.4)
activesupport (= 4.1.4)
arel (~> 5.0.0)
activesupport (4.1.4)
i18n (~> 0.6, >= 0.6.9)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
arel (5.0.1.20140414130214)
builder (3.2.2)
bump (0.5.0)
diff-lcs (1.2.5)
i18n (0.6.9)
json (1.8.1)
minitest (5.4.0)
rake (10.3.2)
rspec (3.0.0)
rspec-core (~> 3.0.0)
rspec-expectations (~> 3.0.0)
rspec-mocks (~> 3.0.0)
rspec-core (3.0.2)
rspec-support (~> 3.0.0)
rspec-expectations (3.0.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.0.0)
rspec-mocks (3.0.2)
rspec-support (~> 3.0.0)
rspec-support (3.0.2)
sqlite3 (1.3.9)
thread_safe (0.3.4)
tzinfo (1.2.1)
thread_safe (~> 0.1)
wwtd (0.5.5)
PLATFORMS
ruby
DEPENDENCIES
activerecord
bump
fast_gettext!
i18n
rake
rspec
sqlite3
wwtd
BUNDLED WITH
1.10.6
fast_gettext-1.0.0/Rakefile 0000664 0000000 0000000 00000000736 12607537372 0015700 0 ustar 00root root 0000000 0000000 require 'bundler/setup'
require 'bundler/gem_tasks'
require 'bump/tasks'
require 'wwtd/tasks'
task :default => "wwtd:local"
task :spec do
sh "rspec spec"
end
task :benchmark do
puts "Running on #{RUBY_DESCRIPTION}"
%w[baseline ideal fast_gettext original i18n_simple].each do |bench|
puts `ruby -I. benchmark/#{bench}.rb`
puts ""
end
end
task :namespaces do
puts `ruby benchmark/namespace/original.rb`
puts `ruby benchmark/namespace/fast_gettext.rb`
end
fast_gettext-1.0.0/Readme.md 0000664 0000000 0000000 00000023252 12607537372 0015750 0 ustar 00root root 0000000 0000000 FastGettext
===========
GetText but 3.5 x faster, 560 x less memory, simple, clean namespace (7 vs 34) and threadsafe!
It supports multiple backends (.mo, .po, .yml files, Database(ActiveRecord + any other), Chain, Loggers) and can easily be extended.
[Example Rails application](https://github.com/grosser/gettext_i18n_rails_example)
Comparison
==========
|
Hash |
FastGettext |
GetText |
ActiveSupport I18n::Simple |
Speed* |
0.82s |
1.36s |
4.88s |
21.77s |
RAM* |
4K |
8K |
4480K |
10100K |
Included backends |
|
db, yml, mo, po, logger, chain |
mo |
yml (db/key-value/po/chain in other I18n backends) |
*50.000 translations with ruby enterprise 1.8.6 through `rake benchmark`
Setup
=====
### 1. Install
sudo gem install fast_gettext
### 2. Add a translation repository
From mo files (traditional/default)
FastGettext.add_text_domain('my_app',:path => 'locale')
Or po files (less maintenance than mo)
FastGettext.add_text_domain('my_app',:path => 'locale', :type => :po)
# :ignore_fuzzy => true to not use fuzzy translations
# :report_warning => false to hide warnings about obsolete/fuzzy translations
Or yaml files (use I18n syntax/indentation)
FastGettext.add_text_domain('my_app', :path => 'config/locales', :type => :yaml)
Or database (scaleable, good for many locales/translators)
# db access is cached <-> only first lookup hits the db
require "fast_gettext/translation_repository/db"
FastGettext::TranslationRepository::Db.require_models #load and include default models
FastGettext.add_text_domain('my_app', :type => :db, :model => TranslationKey)
### 3. Choose text domain and locale for translation
Do this once in every Thread. (e.g. Rails -> ApplicationController)
FastGettext.text_domain = 'my_app'
FastGettext.available_locales = ['de','en','fr','en_US','en_UK'] # only allow these locales to be set (optional)
FastGettext.locale = 'de'
### 4. Start translating
include FastGettext::Translation
_('Car') == 'Auto'
_('not-found') == 'not-found'
s_('Namespace|not-found') == 'not-found'
n_('Axis','Axis',3) == 'Achsen' #German plural of Axis
_('Hello %{name}!') % {:name => "Pete"} == 'Hello Pete!'
Managing translations
============
### mo/po-files
Generate .po or .mo files using GetText parser (example tasks at [gettext_i18n_rails](http://github.com/grosser/gettext_i18n_rails))
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
FastGettext.add_text_domain('my_app',:path=>'locale')
Use the [original GetText](http://github.com/mutoh/gettext) to create and manage po/mo-files.
(Work on a po/mo parser & reader that is easier to use has started, contributions welcome @ [get_pomo](http://github.com/grosser/get_pomo) )
###Database
[Example migration for ActiveRecord](http://github.com/grosser/fast_gettext/blob/master/examples/db/migration.rb)
The default plural seperator is `||||` but you may overwrite it (or suggest a better one..).
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.
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.
To manage translations via a Web GUI, use a [Rails application and the translation_db_engine](http://github.com/grosser/translation_db_engine)
Rails
=======================
Try the [gettext_i18n_rails plugin](http://github.com/grosser/gettext_i18n_rails), it simplifies the setup.
Try the [translation_db_engine](http://github.com/grosser/translation_db_engine), to manage your translations in a db.
Setting `available_locales`,`text_domain` or `locale` will not work inside the `evironment.rb`,
since it runs in a different thread then e.g. controllers, so set them inside your application_controller.
#environment.rb after initializers
Object.send(:include,FastGettext::Translation)
FastGettext.add_text_domain('accounting',:path=>'locale')
FastGettext.add_text_domain('frontend',:path=>'locale')
...
#application_controller.rb
class ApplicationController ...
include FastGettext::Translation
before_filter :set_locale
def set_locale
FastGettext.available_locales = ['de','en',...]
FastGettext.text_domain = 'frontend'
FastGettext.set_locale(params[:locale] || session[:locale] || request.env['HTTP_ACCEPT_LANGUAGE'])
session[:locale] = I18n.locale = FastGettext.locale
end
Advanced features
=================
### Abnormal pluralisation
Plurals are selected by index, think of it as `['car', 'cars'][index]`
A pluralisation rule decides which form to use e.g. in english its `count == 1 ? 0 : 1`.
If you have any languages that do not fit this rule, you have to add a custom pluralisation rule.
Via Ruby:
FastGettext.pluralisation_rule = lambda{|count| count > 5 ? 1 : (count > 2 ? 0 : 2)}
Via mo/pofile:
Plural-Forms: nplurals=2; plural=n==2?3:4;
[Plural expressions for all languages](http://translate.sourceforge.net/wiki/l10n/pluralforms).
###default_text_domain
If you only use one text domain, setting `FastGettext.default_text_domain = 'app'`
is sufficient and no more `text_domain=` is needed
###default_locale
If the simple rule of "first `availble_locale` or 'en'" is not suficcient for you, set `FastGettext.default_locale = 'de'`.
###default_available_locales
Fallback when no available_locales are set
###Chains
You can use any number of repositories to find a translation. Simply add them to a chain and when
the first cannot translate a given key, the next is asked and so forth.
repos = [
FastGettext::TranslationRepository.build('new', :path=>'....'),
FastGettext::TranslationRepository.build('old', :path=>'....')
]
FastGettext.add_text_domain 'combined', :type=>:chain, :chain=>repos
###Logger
When you want to know which keys could not be translated or were used, add a Logger to a Chain:
repos = [
FastGettext::TranslationRepository.build('app', :path=>'....')
FastGettext::TranslationRepository.build('logger', :type=>:logger, :callback=>lambda{|key_or_array_of_ids| ... }),
}
FastGettext.add_text_domain 'combined', :type=>:chain, :chain=>repos
If the Logger is in position #1 it will see all translations, if it is in position #2 it will only see the unfound.
Unfound may not always mean missing, if you choose not to translate a word because the key is a good translation, it will appear nevertheless.
A lambda or anything that responds to `call` will do as callback. A good starting point may be `examples/missing_translations_logger.rb`.
###Plugins
Want a xml version ?
Write your own TranslationRepository!
#fast_gettext/translation_repository/xxx.rb
module FastGettext
module TranslationRepository
class Wtf
define initialize(name,options), [key], plural(*keys) and
either inherit from TranslationRepository::Base or define available_locales and pluralisation_rule
end
end
end
###Multi domain support
If you have more than one gettext domain, there are two sets of functions
available:
include FastGettext::TranslationMultidomain
d_("domainname", "string") # finds 'string' in domain domainname
dn_("domainname", "string", "strings", 1) # ditto
# etc.
These are helper methods so you don't need to write:
FastGettext.text_domain = "domainname"
_("string")
It is useful in Rails plugins in the views for example. The second set of
functions are D functions which search for string in _all_ domains. If there
are multiple translations in different domains, it returns them in random
order (depends on the Ruby hash implementation):
include FastGettext::TranslationMultidomain
D_("string") # finds 'string' in any domain
# etc.
FAQ
===
- [Problems with ActiveRecord messages?](http://wiki.github.com/grosser/fast_gettext/activerecord)
- [Iconv require error in 1.9.2](http://exceptionz.wordpress.com/2010/02/03/how-to-fix-the-iconv-require-error-in-ruby-1-9)
TODO
====
- Add a fallback for Iconv.conv in ruby 1.9.4 -> lib/fast_gettext/vendor/iconv
- YML backend that reads ActiveSupport::I18n files
Author
======
Mo/Po-file parsing from Masao Mutoh, see vendor/README
### [Contributors](http://github.com/grosser/fast_gettext/contributors)
- [geekq](http://www.innoq.com/blog/vd)
- [Matt Sanford](http://blog.mzsanford.com)
- [Antonio Terceiro](http://softwarelivre.org/terceiro)
- [J. Pablo Fernández](http://pupeno.com)
- Rudolf Gavlas
- [Ramón Cahenzli](http://www.psy-q.ch)
- [Rainux Luo](http://rainux.org)
- [Dmitry Borodaenko](https://github.com/angdraug)
- [Kouhei Sutou](https://github.com/kou)
- [Hoang Nghiem](https://github.com/hoangnghiem)
- [Costa Shapiro](https://github.com/costa)
- [Jamie Dyer](https://github.com/kernow)
- [Stephan Kulow](https://github.com/coolo)
- [Fotos Georgiadis](https://github.com/fotos)
- [Lukáš Zapletal](https://github.com/lzap)
- [Dominic Cleal](https://github.com/domcleal)
[Michael Grosser](http://grosser.it)
michael@grosser.it
License: MIT, some vendor parts under the same license terms as Ruby (see headers)
[](https://travis-ci.org/grosser/fast_gettext)
fast_gettext-1.0.0/benchmark/ 0000775 0000000 0000000 00000000000 12607537372 0016157 5 ustar 00root root 0000000 0000000 fast_gettext-1.0.0/benchmark/base.rb 0000664 0000000 0000000 00000002041 12607537372 0017413 0 ustar 00root root 0000000 0000000 require 'benchmark'
$LOAD_PATH.unshift 'lib'
RUNS = 50_0000
DEFAULTS = {:memory=>0}
def locale_folder(domain)
path = case domain
when 'test' then File.join(File.expand_path(File.dirname(__FILE__)),'..','spec','locale')
when 'large' then File.join(File.expand_path(File.dirname(__FILE__)),'locale')
end
mo = File.join(path,'de','LC_MESSAGES',"#{domain}.mo")
raise unless File.exist?(mo)
path
end
def results_test(&block)
print "#{(result(&block)).to_s.strip.split(' ').first}s / #{memory}K <-> "
end
def results_large
print "#{(result {_('login') == 'anmelden'}).to_s.strip.split(' ').first}s / #{memory}K"
puts ""
end
def result
result =Benchmark.measure do
RUNS.times do
raise "not translated" unless yield
end
end
result
end
def memory
pid = Process.pid
if RUBY_PLATFORM.downcase.include?("darwin")
map = `vmmap #{pid}`
else
map = `pmap -d #{pid}`
end
map.split("\n").last.strip.squeeze(' ').split(' ')[3].to_i - DEFAULTS[:memory]
end
DEFAULTS[:memory] = memory + 4 #4 => 0 for base calls
fast_gettext-1.0.0/benchmark/baseline.rb 0000664 0000000 0000000 00000000137 12607537372 0020267 0 ustar 00root root 0000000 0000000 require_relative 'base'
puts "Baseline: (doing nothing in a loop)"
results_test{true}
puts ""
fast_gettext-1.0.0/benchmark/fast_gettext.rb 0000664 0000000 0000000 00000000756 12607537372 0021215 0 ustar 00root root 0000000 0000000 require_relative 'base'
require 'fast_gettext'
include FastGettext::Translation
FastGettext.available_locales = ['de','en']
FastGettext.locale = 'de'
puts "FastGettext:"
FastGettext.add_text_domain('test',:path=>locale_folder('test'))
FastGettext.text_domain = 'test'
results_test{_('car') == 'Auto'}
#i cannot add the large file, since its an internal applications mo file
FastGettext.add_text_domain('large',:path=>locale_folder('large'))
FastGettext.text_domain = 'large'
results_large
fast_gettext-1.0.0/benchmark/i18n_simple.rb 0000664 0000000 0000000 00000000407 12607537372 0020635 0 ustar 00root root 0000000 0000000 require_relative 'base'
require 'active_support'
I18n.backend = I18n::Backend::Simple.new
I18n.load_path = ['benchmark/locale/de.yml']
I18n.locale = :de
puts "ActiveSupport I18n::Backend::Simple :"
results_test{I18n.translate('activerecord.models.car')=='Auto'}
fast_gettext-1.0.0/benchmark/ideal.rb 0000664 0000000 0000000 00000001116 12607537372 0017561 0 ustar 00root root 0000000 0000000 require_relative 'base'
module FastestGettext
def set_domain(folder,domain,locale)
@data = {}
require 'fast_gettext/vendor/mofile'
FastGettext::GetText::MOFile.open(File.join(folder,locale,'LC_MESSAGES',"#{domain}.mo"), "UTF-8").each{|k,v|@data[k]=v}
end
def _(word)
@data[word]
end
end
include FastestGettext
set_domain(locale_folder('test'),'test','de')
puts "Ideal: (primitive Hash lookup)"
results_test{_('car') == 'Auto'}
#i cannot add the large file, since its an internal applications mo file
set_domain(locale_folder('large'),'large','de')
results_large
fast_gettext-1.0.0/benchmark/locale/ 0000775 0000000 0000000 00000000000 12607537372 0017416 5 ustar 00root root 0000000 0000000 fast_gettext-1.0.0/benchmark/locale/de.yml 0000664 0000000 0000000 00000007124 12607537372 0020535 0 ustar 00root root 0000000 0000000 # German translations for Ruby on Rails
# by Clemens Kofler (clemens@railway.at)
de:
date:
formats:
default: "%d.%m.%Y"
short: "%e. %b"
long: "%e. %B %Y"
only_day: "%e"
day_names: [Sonntag, Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag]
abbr_day_names: [So, Mo, Di, Mi, Do, Fr, Sa]
month_names: [~, Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember]
abbr_month_names: [~, Jan, Feb, Mär, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez]
time:
formats:
default: "%A, %e. %B %Y, %H:%M Uhr"
short: "%e. %B, %H:%M Uhr"
long: "%A, %e. %B %Y, %H:%M Uhr"
time: "%H:%M"
am: "vormittags"
pm: "nachmittags"
datetime:
distance_in_words:
half_a_minute: 'eine halbe Minute'
less_than_x_seconds:
zero: 'weniger als 1 Sekunde'
one: 'weniger als 1 Sekunde'
other: 'weniger als {{count}} Sekunden'
x_seconds:
one: '1 Sekunde'
other: '{{count}} Sekunden'
less_than_x_minutes:
zero: 'weniger als 1 Minute'
one: 'weniger als eine Minute'
other: 'weniger als {{count}} Minuten'
x_minutes:
one: '1 Minute'
other: '{{count}} Minuten'
about_x_hours:
one: 'etwa 1 Stunde'
other: 'etwa {{count}} Stunden'
x_days:
one: '1 Tag'
other: '{{count}} Tage'
about_x_months:
one: 'etwa 1 Monat'
other: 'etwa {{count}} Monate'
x_months:
one: '1 Monat'
other: '{{count}} Monate'
about_x_years:
one: 'etwa 1 Jahr'
other: 'etwa {{count}} Jahre'
over_x_years:
one: 'mehr als 1 Jahr'
other: 'mehr als {{count}} Jahre'
number:
format:
precision: 2
separator: ','
delimiter: '.'
currency:
format:
unit: '€'
format: '%n%u'
separator:
delimiter:
precision:
percentage:
format:
delimiter: ""
precision:
format:
delimiter: ""
human:
format:
delimiter: ""
precision: 1
support:
array:
sentence_connector: "und"
skip_last_comma: true
activerecord:
errors:
template:
header:
one: "Konnte dieses {{model}} Objekt nicht speichern: 1 Fehler."
other: "Konnte dieses {{model}} Objekt nicht speichern: {{count}} Fehler."
body: "Bitte überprüfen Sie die folgenden Felder:"
format:
seperator: ' '
messages:
inclusion: "ist kein gültiger Wert"
exclusion: "ist nicht verfügbar"
invalid: "ist nicht gültig"
confirmation: "stimmt nicht mit der Bestätigung überein"
accepted: "muss akzeptiert werden"
empty: "muss ausgefüllt werden"
blank: "muss ausgefüllt werden"
too_long: "ist zu lang (nicht mehr als {{count}} Zeichen)"
too_short: "ist zu kurz (nicht weniger als {{count}} Zeichen)"
wrong_length: "hat die falsche Länge (muss genau {{count}} Zeichen haben)"
taken: "ist bereits vergeben"
not_a_number: "ist keine Zahl"
greater_than: "muss größer als {{count}} sein"
greater_than_or_equal_to: "muss größer oder gleich {{count}} sein"
equal_to: "muss genau {{count}} sein"
less_than: "muss kleiner als {{count}} sein"
less_than_or_equal_to: "muss kleiner oder gleich {{count}} sein"
odd: "muss ungerade sein"
even: "muss gerade sein"
models:
car: 'BAUTO'
cars: 'CAUTO'
Car: 'DAUTO'
models:
car: 'Auto'
fast_gettext-1.0.0/benchmark/locale/de/ 0000775 0000000 0000000 00000000000 12607537372 0020006 5 ustar 00root root 0000000 0000000 fast_gettext-1.0.0/benchmark/locale/de/LC_MESSAGES/ 0000775 0000000 0000000 00000000000 12607537372 0021573 5 ustar 00root root 0000000 0000000 fast_gettext-1.0.0/benchmark/locale/de/LC_MESSAGES/large.mo 0000664 0000000 0000000 00000334070 12607537372 0023231 0 ustar 00root root 0000000 0000000 ! B X X ! "X DX YX tX X C X > X ? ,Y lY A Y * Y Y = Z LZ
aZ 5 lZ
Z 5 Z Z Z
[ [ [
"[ -[ 4[ F[
][ k[
{[ , [ ( [ D [ $\ 1\
@\ K\ \\ x\ \ \ \ \ \ \ \ \ \ ] ]
] !] ?] U] i] ) r] ] ) ] ] ] ] ] ^ ^ 4^ L^ `^ r^ ^ ^ ^
^
^ ^ ^ ^
^ ^ ^ ^
_ _ +_
<_ J_ \_ d_ l_ _ _ _ _ _ _ g` ] {` ` 2 ` a 3a Ea Ua la |a ' a &