published at 2015-02-24
written by Ivan Stana

Howto: Extending FastGettext with custom translation repository

Scenario: I want to have translations for one language in more files than one file. Current FastGettext::TranslationRepository::Yaml can't handle this. So we create our own Yamlr module.

Readme.md tells something about custom modules, but one must be skilled to understand how to use this knowledge. This howto aims to fill this gap.

1. Modify load path

I know how to do this in Rails.

# add to config/application.rb
config.autoload_paths += %W(#{config.root}/lib)

This won't load all files from /lib directory, but tells Rails where to search when you call require function.

2. Add module to new load path

Create lib/fast_gettext/translation_repository in Rails application. Now we move our yaml_recursive.rb here. Yes, only one file here.

The source is modified version of the original Yaml module:

require 'fast_gettext/translation_repository/base'
require 'yaml'
require 'active_support/core_ext/hash/deep_merge.rb'

module FastGettext
  module TranslationRepository
    # Responsibility:
    #  - find and store yaml files
    #  - provide access to translations in yaml files
    class Yamlr < Base
      def initialize(name,options={})
        super
        reload
      end

      def available_locales
        @files.keys
      end

      def plural(*keys)
        ['one', 'other', 'plural2', 'plural3'].map do |name|
          self[yaml_dot_notation(keys.first, name)]
        end
      end

      def pluralisation_rule
        self['pluralisation_rule'] ? lambda{|n| eval(self['pluralisation_rule']) } : nil
      end

      def reload
        find_and_store_files(@options)
        super
      end

      protected

      MAX_FIND_DEPTH = 10

      def find_and_store_files(options)
        @files = {}
        path = options[:path] || 'config/locales'
        Dir["#{path}/**/*.yml"].each do |yaml_file|
                    @files.deep_merge!(load_yaml(yaml_file))
        end
                @files
      end

      def current_translations
        @files[FastGettext.locale] || super
      end

      # Given a yaml file return a hash of key -> translation
      def load_yaml(file)
        yaml = YAML.load_file(file)
                yaml.keys.reduce({}) do |processed, locale|
                    processed[locale] ||= {}
                    processed[locale].merge!(yaml_hash_to_dot_notation(yaml[locale]))
                    processed
                end
      end

      def yaml_hash_to_dot_notation(yaml_hash)
        add_yaml_key({}, nil, yaml_hash)
      end

      def add_yaml_key(result, prefix, hash)
        hash.each_pair do |key, value|
          if value.kind_of?(Hash)
            add_yaml_key(result, yaml_dot_notation(prefix, key), value)
          else
            result[yaml_dot_notation(prefix, key)] = value
          end
        end
        result
      end

      def yaml_dot_notation(a,b)
        a ? "#{a}.#{b}" : b
      end
    end
  end
end

Now we can read I18n translations too. To use these translations with fast_gettext you need to override calls to I18n. (TODO how?)

3. Enjoy

rails c

...
require 'fast_gettext/translation_repository/yaml_recursive'
#=> true
FastGettext.add_text_domain('lala', path: 'config/locales', type: :yamlr)
#=> { ...our translations ... }

4. Debug

From the initializer:

puts FastGettext.add_text_domain('lala', path: 'config/locales', type: :yamlr).inspect