pepe-i18n 0.2.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.
Files changed (61) hide show
  1. data/CHANGELOG.textile +57 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.textile +42 -0
  4. data/Rakefile +21 -0
  5. data/VERSION +1 -0
  6. data/lib/i18n.rb +270 -0
  7. data/lib/i18n/backend/base.rb +251 -0
  8. data/lib/i18n/backend/cache.rb +71 -0
  9. data/lib/i18n/backend/chain.rb +64 -0
  10. data/lib/i18n/backend/fallbacks.rb +53 -0
  11. data/lib/i18n/backend/gettext.rb +65 -0
  12. data/lib/i18n/backend/pluralization.rb +56 -0
  13. data/lib/i18n/backend/simple.rb +23 -0
  14. data/lib/i18n/exceptions.rb +61 -0
  15. data/lib/i18n/gettext.rb +25 -0
  16. data/lib/i18n/helpers/gettext.rb +35 -0
  17. data/lib/i18n/locale/fallbacks.rb +100 -0
  18. data/lib/i18n/locale/tag.rb +27 -0
  19. data/lib/i18n/locale/tag/parents.rb +24 -0
  20. data/lib/i18n/locale/tag/rfc4646.rb +78 -0
  21. data/lib/i18n/locale/tag/simple.rb +44 -0
  22. data/lib/i18n/string.rb +95 -0
  23. data/test/all.rb +5 -0
  24. data/test/api/basics.rb +15 -0
  25. data/test/api/interpolation.rb +85 -0
  26. data/test/api/lambda.rb +52 -0
  27. data/test/api/link.rb +47 -0
  28. data/test/api/localization/date.rb +65 -0
  29. data/test/api/localization/date_time.rb +63 -0
  30. data/test/api/localization/lambda.rb +26 -0
  31. data/test/api/localization/time.rb +63 -0
  32. data/test/api/pluralization.rb +37 -0
  33. data/test/api/translation.rb +51 -0
  34. data/test/backend/cache/cache_test.rb +57 -0
  35. data/test/backend/chain/api_test.rb +80 -0
  36. data/test/backend/chain/chain_test.rb +64 -0
  37. data/test/backend/fallbacks/api_test.rb +79 -0
  38. data/test/backend/fallbacks/fallbacks_test.rb +29 -0
  39. data/test/backend/pluralization/api_test.rb +81 -0
  40. data/test/backend/pluralization/pluralization_test.rb +39 -0
  41. data/test/backend/simple/all.rb +5 -0
  42. data/test/backend/simple/api_test.rb +90 -0
  43. data/test/backend/simple/lookup_test.rb +24 -0
  44. data/test/backend/simple/setup.rb +147 -0
  45. data/test/backend/simple/translations_test.rb +89 -0
  46. data/test/fixtures/locales/de.po +61 -0
  47. data/test/fixtures/locales/en.rb +3 -0
  48. data/test/fixtures/locales/en.yml +3 -0
  49. data/test/fixtures/locales/plurals.rb +112 -0
  50. data/test/gettext/api_test.rb +78 -0
  51. data/test/gettext/backend_test.rb +35 -0
  52. data/test/i18n_exceptions_test.rb +97 -0
  53. data/test/i18n_load_path_test.rb +23 -0
  54. data/test/i18n_test.rb +163 -0
  55. data/test/locale/fallbacks_test.rb +128 -0
  56. data/test/locale/tag/rfc4646_test.rb +147 -0
  57. data/test/locale/tag/simple_test.rb +35 -0
  58. data/test/string_test.rb +94 -0
  59. data/test/test_helper.rb +71 -0
  60. data/test/with_options.rb +34 -0
  61. metadata +151 -0
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+
3
+ # This module allows you to easily cache all responses from the backend - thus
4
+ # speeding up the I18n aspects of your application quite a bit.
5
+ #
6
+ # To enable caching you can simply include the Cache module to the Simple
7
+ # backend - or whatever other backend you are using:
8
+ #
9
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Cache)
10
+ #
11
+ # You will also need to set a cache store implementation that you want to use:
12
+ #
13
+ # I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
14
+ #
15
+ # You can use any cache implementation you want that provides the same API as
16
+ # ActiveSupport::Cache (only the methods #fetch and #write are being used).
17
+ #
18
+ # The cache_key implementation assumes that you only pass values to
19
+ # I18n.translate that return a valid key from #hash (see
20
+ # http://www.ruby-doc.org/core/classes/Object.html#M000337).
21
+ module I18n
22
+ class << self
23
+ @@cache_store = nil
24
+ @@cache_namespace = nil
25
+
26
+ def cache_store
27
+ @@cache_store
28
+ end
29
+
30
+ def cache_store=(store)
31
+ @@cache_store = store
32
+ end
33
+
34
+ def cache_namespace
35
+ @@cache_namespace
36
+ end
37
+
38
+ def cache_namespace=(namespace)
39
+ @@cache_namespace = namespace
40
+ end
41
+
42
+ def perform_caching?
43
+ !cache_store.nil?
44
+ end
45
+ end
46
+
47
+ module Backend
48
+ module Cache
49
+ def translate(*args)
50
+ I18n.perform_caching? ? fetch(*args) { super } : super
51
+ end
52
+
53
+ protected
54
+
55
+ def fetch(*args, &block)
56
+ result = I18n.cache_store.fetch(cache_key(*args), &block)
57
+ raise result if result.is_a?(Exception)
58
+ result
59
+ rescue MissingTranslationData => exception
60
+ I18n.cache_store.write(cache_key(*args), exception)
61
+ raise exception
62
+ end
63
+
64
+ def cache_key(*args)
65
+ # this assumes that only simple, native Ruby values are passed to I18n.translate
66
+ keys = ['i18n', I18n.cache_namespace, args.hash]
67
+ keys.compact.join('-')
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+
3
+ module I18n
4
+ module Backend
5
+ # Backend that chains multiple other backends and checks each of them when
6
+ # a translation needs to be looked up. This is useful when you want to use
7
+ # standard translations with a Simple backend but store custom application
8
+ # translations in a database or other backends.
9
+ #
10
+ # To use the Chain backend instantiate it and set it to the I18n module.
11
+ # You can add chained backends through the initializer or backends
12
+ # accessor:
13
+ #
14
+ # # preserves the existing Simple backend set to I18n.backend
15
+ # I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)
16
+ #
17
+ # The implementation assumes that all backends added to the Chain implement
18
+ # a lookup method with the same API as Simple backend does.
19
+ class Chain < Base
20
+ attr_accessor :backends
21
+
22
+ def initialize(*backends)
23
+ self.backends = backends
24
+ end
25
+
26
+ def reload!
27
+ backends.each { |backend| backend.reload! }
28
+ end
29
+
30
+ def store_translations(locale, data)
31
+ backends.first.store_translations(locale, data)
32
+ end
33
+
34
+ def available_locales
35
+ backends.map { |backend| backend.available_locales }.flatten.uniq
36
+ end
37
+
38
+ def localize(locale, object, format = :default, options = {})
39
+ backends.each do |backend|
40
+ begin
41
+ result = backend.localize(locale, object, format, options) and return result
42
+ rescue MissingTranslationData
43
+ end
44
+ end or nil
45
+ end
46
+
47
+ protected
48
+
49
+ def lookup(locale, key, scope = [], separator = nil)
50
+ return unless key
51
+ result = {}
52
+ backends.each do |backend|
53
+ entry = backend.lookup(locale, key, scope, separator)
54
+ if entry.is_a?(Hash)
55
+ result.merge!(entry)
56
+ elsif !entry.nil?
57
+ return entry
58
+ end
59
+ end
60
+ result.empty? ? nil : result
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'i18n/locale/fallbacks'
4
+
5
+ # I18n locale fallbacks are useful when you want your application to use
6
+ # translations from other locales when translations for the current locale are
7
+ # missing. E.g. you might want to use :en translations when translations in
8
+ # your applications main locale :de are missing.
9
+ #
10
+ # To enable locale fallbacks you can simply include the Fallbacks module to
11
+ # the Simple backend - or whatever other backend you are using:
12
+ #
13
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
14
+ module I18n
15
+ @@fallbacks = nil
16
+
17
+ class << self
18
+ # Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
19
+ def fallbacks
20
+ @@fallbacks ||= I18n::Locale::Fallbacks.new
21
+ end
22
+
23
+ # Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
24
+ def fallbacks=(fallbacks)
25
+ @@fallbacks = fallbacks
26
+ end
27
+ end
28
+
29
+ module Backend
30
+ module Fallbacks
31
+ # Overwrites the Base backend translate method so that it will try each
32
+ # locale given by I18n.fallbacks for the given locale. E.g. for the
33
+ # locale :"de-DE" it might try the locales :"de-DE", :de and :en
34
+ # (depends on the fallbacks implementation) until it finds a result with
35
+ # the given options. If it does not find any result for any of the
36
+ # locales it will then raise a MissingTranslationData exception as
37
+ # usual.
38
+ #
39
+ # The default option takes precedence over fallback locales, i.e. it
40
+ # will first evaluate a given default option before falling back to
41
+ # another locale.
42
+ def translate(locale, key, options = {})
43
+ I18n.fallbacks[locale].each do |fallback|
44
+ begin
45
+ result = super(fallback, key, options) and return result
46
+ rescue I18n::MissingTranslationData
47
+ end
48
+ end
49
+ raise(I18n::MissingTranslationData.new(locale, key, options))
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+
3
+ require 'i18n/gettext'
4
+ require File.expand_path(File.dirname(__FILE__) + '/../../../vendor/po_parser.rb')
5
+
6
+ # Experimental support for using Gettext po files to store translations.
7
+ #
8
+ # To use this you can simply include the module to the Simple backend - or
9
+ # whatever other backend you are using.
10
+ #
11
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Gettext)
12
+ #
13
+ # Now you should be able to include your Gettext translation (*.po) files to
14
+ # the I18n.load_path so they're loaded to the backend and you can use them as
15
+ # usual:
16
+ #
17
+ # I18n.load_path += Dir["path/to/locales/*.po"]
18
+ #
19
+ # Following the Gettext convention this implementation expects that your
20
+ # translation files are named by their locales. E.g. the file en.po would
21
+ # contain the translations for the English locale.
22
+ module I18n
23
+ module Backend
24
+ module Gettext
25
+ class PoData < Hash
26
+ def set_comment(msgid_or_sym, comment)
27
+ # ignore
28
+ end
29
+ end
30
+
31
+ protected
32
+ def load_po(filename)
33
+ locale = ::File.basename(filename, '.po').to_sym
34
+ data = normalize(locale, parse(filename))
35
+ { locale => data }
36
+ end
37
+
38
+ def parse(filename)
39
+ GetText::PoParser.new.parse(::File.read(filename), PoData.new)
40
+ end
41
+
42
+ def normalize(locale, data)
43
+ data.inject({}) do |result, (key, value)|
44
+ key, value = normalize_pluralization(locale, key, value) if key.index("\000")
45
+ result[key] = value
46
+ result
47
+ end
48
+ end
49
+
50
+ def normalize_pluralization(locale, key, value)
51
+ # FIXME po_parser includes \000 chars that can not be turned into Symbols
52
+ key = key.dup.gsub("\000", I18n::Gettext::PLURAL_SEPARATOR)
53
+
54
+ keys = I18n::Gettext.plural_keys(locale)
55
+ values = value.split("\000")
56
+ raise "invalid number of plurals: #{values.size}, keys: #{keys.inspect}" if values.size != keys.size
57
+
58
+ result = {}
59
+ values.each_with_index { |value, ix| result[keys[ix]] = value }
60
+ [key, result]
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ # I18n locale fallbacks are useful when you want your application to use
4
+ # translations from other locales when translations for the current locale are
5
+ # missing. E.g. you might want to use :en translations when translations in
6
+ # your applications main locale :de are missing.
7
+ #
8
+ # To enable locale specific pluralizations you can simply include the
9
+ # Pluralization module to the Simple backend - or whatever other backend you
10
+ # are using.
11
+ #
12
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
13
+ #
14
+ # You also need to make sure to provide pluralization algorithms to the
15
+ # backend, i.e. include them to your I18n.load_path accordingly.
16
+ module I18n
17
+ module Backend
18
+ module Pluralization
19
+ # Overwrites the Base backend translate method so that it will check the
20
+ # translation meta data space (:i18n) for locale specific pluralizers
21
+ # and use them to pluralize the given entry.
22
+ #
23
+ # Pluralizers are expected to respond to #call(entry, count) and return
24
+ # a pluralization key. Valid keys depend on the translation data hash
25
+ # (entry) but it is generally recommended to follow CLDR's style, i.e.
26
+ # return one of the keys :zero, :one, :few, :many, :other.
27
+ #
28
+ # The :zero key is always picked directly when count equals 0 AND the
29
+ # translation data has the key :zero. This way translators are free to
30
+ # either pick a special :zero translation even for languages where the
31
+ # pluralizer does not return a :zero key.
32
+ def pluralize(locale, entry, count)
33
+ return entry unless entry.is_a?(Hash) and count
34
+
35
+ pluralizer = pluralizer(locale)
36
+ if pluralizer.respond_to?(:call)
37
+ key = count == 0 && entry.has_key?(:zero) ? :zero : pluralizer.call(count)
38
+ raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
39
+ entry[key]
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ protected
46
+
47
+ def pluralizers
48
+ @pluralizers ||= {}
49
+ end
50
+
51
+ def pluralizer(locale)
52
+ pluralizers[locale] ||= lookup(locale, :"i18n.pluralize")
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require 'i18n/backend/base'
4
+
5
+ # Stub class for the Simple backend. The actual implementation is provided by
6
+ # the backend Base class. This makes it easier to extend the Simple backend's
7
+ # behaviour by including modules. E.g.:
8
+ #
9
+ # module I18n::Backend::Pluralization
10
+ # def pluralize(*args)
11
+ # # extended pluralization logic
12
+ # super
13
+ # end
14
+ # end
15
+ #
16
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
17
+
18
+ module I18n
19
+ module Backend
20
+ class Simple < Base
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+
3
+ class KeyError < IndexError
4
+ def initialize(message = nil)
5
+ super(message || "key not found")
6
+ end
7
+ end unless defined?(KeyError)
8
+
9
+ module I18n
10
+ class ArgumentError < ::ArgumentError; end
11
+
12
+ class InvalidLocale < ArgumentError
13
+ attr_reader :locale
14
+ def initialize(locale)
15
+ @locale = locale
16
+ super "#{locale.inspect} is not a valid locale"
17
+ end
18
+ end
19
+
20
+ class MissingTranslationData < ArgumentError
21
+ attr_reader :locale, :key, :options
22
+ def initialize(locale, key, options)
23
+ @key, @locale, @options = key, locale, options
24
+ keys = I18n.send(:normalize_translation_keys, locale, key, options[:scope])
25
+ keys << 'no key' if keys.size < 2
26
+ super "translation missing: #{keys.join(', ')}"
27
+ end
28
+ end
29
+
30
+ class InvalidPluralizationData < ArgumentError
31
+ attr_reader :entry, :count
32
+ def initialize(entry, count)
33
+ @entry, @count = entry, count
34
+ super "translation data #{entry.inspect} can not be used with :count => #{count}"
35
+ end
36
+ end
37
+
38
+ class MissingInterpolationArgument < ArgumentError
39
+ attr_reader :values, :string
40
+ def initialize(values, string)
41
+ @values, @string = values, string
42
+ super "missing interpolation argument in #{string.inspect} (#{values.inspect} given)"
43
+ end
44
+ end
45
+
46
+ class ReservedInterpolationKey < ArgumentError
47
+ attr_reader :key, :string
48
+ def initialize(key, string)
49
+ @key, @string = key, string
50
+ super "reserved key #{key.inspect} used in #{string.inspect}"
51
+ end
52
+ end
53
+
54
+ class UnknownFileType < ArgumentError
55
+ attr_reader :type, :filename
56
+ def initialize(type, filename)
57
+ @type, @filename = type, filename
58
+ super "can not load translations from #{filename}, the file type #{type} is not known"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ module I18n
4
+ module Gettext
5
+ PLURAL_SEPARATOR = "\001"
6
+ CONTEXT_SEPARATOR = "\004"
7
+
8
+ @@plural_keys = { :en => [:one, :other] }
9
+
10
+ class << self
11
+ # returns an array of plural keys for the given locale so that we can
12
+ # convert from gettext's integer-index based style
13
+ # TODO move this information to the pluralization module
14
+ def plural_keys(locale)
15
+ @@plural_keys[locale] || @@plural_keys[:en]
16
+ end
17
+
18
+ def extract_scope(msgid, separator = nil)
19
+ scope = msgid.to_s.split(separator || '|')
20
+ msgid = scope.pop
21
+ [scope, msgid]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ module I18n
4
+ module Helpers
5
+ # Implements classical Gettext style accessors. To use this include the
6
+ # module to the global namespace or wherever you want to use it.
7
+ #
8
+ # include I18n::Helpers::Gettext
9
+ module Gettext
10
+ def _(msgid, options = {})
11
+ I18n.t(msgid, { :default => msgid, :separator => '|' }.merge(options))
12
+ end
13
+
14
+ def sgettext(msgid, separator = '|')
15
+ scope, msgid = I18n::Gettext.extract_scope(msgid, separator)
16
+ I18n.t(msgid, :scope => scope, :default => msgid)
17
+ end
18
+
19
+ def pgettext(msgctxt, msgid, separator = I18n::Gettext::CONTEXT_SEPARATOR)
20
+ sgettext([msgctxt, msgid].join(separator), separator)
21
+ end
22
+
23
+ def ngettext(msgid, msgid_plural, n = 1)
24
+ nsgettext(msgid, msgid_plural, n, nil)
25
+ end
26
+
27
+ def nsgettext(msgid, msgid_plural, n = 1, separator = nil)
28
+ scope, msgid = I18n::Gettext.extract_scope(msgid, separator)
29
+ default = { :one => msgid, :other => msgid_plural }
30
+ msgid = [msgid, I18n::Gettext::PLURAL_SEPARATOR, msgid_plural].join
31
+ I18n.t(msgid, :default => default, :count => n, :scope => scope)
32
+ end
33
+ end
34
+ end
35
+ end