translatomatic 0.1.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.
- checksums.yaml +7 -0
 - data/.gitignore +12 -0
 - data/.rspec +3 -0
 - data/.travis.yml +51 -0
 - data/CODE_OF_CONDUCT.md +74 -0
 - data/Gemfile +6 -0
 - data/Gemfile.lock +137 -0
 - data/LICENSE.txt +21 -0
 - data/README.md +74 -0
 - data/Rakefile +6 -0
 - data/bin/console +14 -0
 - data/bin/setup +8 -0
 - data/bin/translatomatic +6 -0
 - data/db/database.yml +9 -0
 - data/db/migrate/201712170000_initial.rb +23 -0
 - data/lib/translatomatic/cli.rb +92 -0
 - data/lib/translatomatic/config.rb +26 -0
 - data/lib/translatomatic/converter.rb +157 -0
 - data/lib/translatomatic/converter_stats.rb +27 -0
 - data/lib/translatomatic/database.rb +105 -0
 - data/lib/translatomatic/escaped_unicode.rb +90 -0
 - data/lib/translatomatic/model/locale.rb +22 -0
 - data/lib/translatomatic/model/text.rb +13 -0
 - data/lib/translatomatic/model.rb +4 -0
 - data/lib/translatomatic/option.rb +24 -0
 - data/lib/translatomatic/resource_file/base.rb +137 -0
 - data/lib/translatomatic/resource_file/html.rb +33 -0
 - data/lib/translatomatic/resource_file/plist.rb +29 -0
 - data/lib/translatomatic/resource_file/properties.rb +60 -0
 - data/lib/translatomatic/resource_file/text.rb +28 -0
 - data/lib/translatomatic/resource_file/xcode_strings.rb +65 -0
 - data/lib/translatomatic/resource_file/xml.rb +64 -0
 - data/lib/translatomatic/resource_file/yaml.rb +80 -0
 - data/lib/translatomatic/resource_file.rb +74 -0
 - data/lib/translatomatic/translation_result.rb +68 -0
 - data/lib/translatomatic/translator/base.rb +47 -0
 - data/lib/translatomatic/translator/frengly.rb +64 -0
 - data/lib/translatomatic/translator/google.rb +30 -0
 - data/lib/translatomatic/translator/microsoft.rb +32 -0
 - data/lib/translatomatic/translator/my_memory.rb +55 -0
 - data/lib/translatomatic/translator/yandex.rb +37 -0
 - data/lib/translatomatic/translator.rb +63 -0
 - data/lib/translatomatic/util.rb +24 -0
 - data/lib/translatomatic/version.rb +3 -0
 - data/lib/translatomatic.rb +27 -0
 - data/translatomatic.gemspec +46 -0
 - metadata +329 -0
 
| 
         @@ -0,0 +1,157 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Translatomatic::Converter
         
     | 
| 
      
 2 
     | 
    
         
            +
              include Translatomatic::Util
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :options
         
     | 
| 
      
 6 
     | 
    
         
            +
                private
         
     | 
| 
      
 7 
     | 
    
         
            +
                include Translatomatic::DefineOptions
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              define_options(
         
     | 
| 
      
 11 
     | 
    
         
            +
                { name: :translator, type: :string, aliases: "-t",
         
     | 
| 
      
 12 
     | 
    
         
            +
                  desc: "The translator implementation",
         
     | 
| 
      
 13 
     | 
    
         
            +
                  enum: Translatomatic::Translator.names },
         
     | 
| 
      
 14 
     | 
    
         
            +
                { name: :dry_run, type: :boolean, aliases: "-n", desc:
         
     | 
| 
      
 15 
     | 
    
         
            +
                  "Print actions without performing translations or writing files" }
         
     | 
| 
      
 16 
     | 
    
         
            +
              )
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              # @return [Translatomatic::ConverterStats] translation statistics
         
     | 
| 
      
 19 
     | 
    
         
            +
              attr_reader :stats
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              # Create a converter to translate files
         
     | 
| 
      
 22 
     | 
    
         
            +
              #
         
     | 
| 
      
 23 
     | 
    
         
            +
              # @param options A hash of converter and/or translator options.
         
     | 
| 
      
 24 
     | 
    
         
            +
              def initialize(options = {})
         
     | 
| 
      
 25 
     | 
    
         
            +
                @dry_run = options[:dry_run]
         
     | 
| 
      
 26 
     | 
    
         
            +
                @translator = options[:translator]
         
     | 
| 
      
 27 
     | 
    
         
            +
                if @translator.kind_of?(String) || @translator.kind_of?(Symbol)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  klass = Translatomatic::Translator.find(@translator)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @translator = klass.new(options)
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
                raise "translator required" unless @translator
         
     | 
| 
      
 32 
     | 
    
         
            +
                @from_db = 0
         
     | 
| 
      
 33 
     | 
    
         
            +
                @from_translator = 0
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
              # @return [Translatomatic::ConverterStats] Translation statistics
         
     | 
| 
      
 37 
     | 
    
         
            +
              def stats
         
     | 
| 
      
 38 
     | 
    
         
            +
                Translatomatic::ConverterStats.new(@from_db, @from_translator)
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              # Translate contents of source_file to the target locale.
         
     | 
| 
      
 42 
     | 
    
         
            +
              # Automatically determines the target filename based on target locale.
         
     | 
| 
      
 43 
     | 
    
         
            +
              #
         
     | 
| 
      
 44 
     | 
    
         
            +
              # @param [String, Translatomatic::ResourceFile] source_file File to translate
         
     | 
| 
      
 45 
     | 
    
         
            +
              # @param [String] to_locale The target locale, e.g. "fr"
         
     | 
| 
      
 46 
     | 
    
         
            +
              # @return [Translatomatic::ResourceFile] The translated resource file
         
     | 
| 
      
 47 
     | 
    
         
            +
              def translate(source_file, to_locale)
         
     | 
| 
      
 48 
     | 
    
         
            +
                if source_file.kind_of?(Translatomatic::ResourceFile::Base)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  source = source_file
         
     | 
| 
      
 50 
     | 
    
         
            +
                else
         
     | 
| 
      
 51 
     | 
    
         
            +
                  source = Translatomatic::ResourceFile.load(source_file)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  raise "unsupported file type #{source_file}" unless source
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                to_locale = parse_locale(to_locale)
         
     | 
| 
      
 56 
     | 
    
         
            +
                target = Translatomatic::ResourceFile.load(source.path)
         
     | 
| 
      
 57 
     | 
    
         
            +
                target.path = source.locale_path(to_locale)
         
     | 
| 
      
 58 
     | 
    
         
            +
                target.locale = to_locale
         
     | 
| 
      
 59 
     | 
    
         
            +
                translate_to_target(source, target)
         
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
              # Translates a resource file and writes results to a target resource file
         
     | 
| 
      
 63 
     | 
    
         
            +
              #
         
     | 
| 
      
 64 
     | 
    
         
            +
              # @param source [Translatomatic::ResourceFile] The source
         
     | 
| 
      
 65 
     | 
    
         
            +
              # @param target [Translatomatic::ResourceFile] The file to write
         
     | 
| 
      
 66 
     | 
    
         
            +
              # @return [Translatomatic::ResourceFile] The translated resource file
         
     | 
| 
      
 67 
     | 
    
         
            +
              def translate_to_target(source, target)
         
     | 
| 
      
 68 
     | 
    
         
            +
                # perform translation
         
     | 
| 
      
 69 
     | 
    
         
            +
                log.info "translating #{source} to #{target}"
         
     | 
| 
      
 70 
     | 
    
         
            +
                properties = translate_properties(source.properties, source.locale, target.locale)
         
     | 
| 
      
 71 
     | 
    
         
            +
                target.properties = properties
         
     | 
| 
      
 72 
     | 
    
         
            +
                target.save unless @dry_run
         
     | 
| 
      
 73 
     | 
    
         
            +
                target
         
     | 
| 
      
 74 
     | 
    
         
            +
              end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              # Translate values in the hash of properties.
         
     | 
| 
      
 77 
     | 
    
         
            +
              # Uses existing translations from the database if available.
         
     | 
| 
      
 78 
     | 
    
         
            +
              #
         
     | 
| 
      
 79 
     | 
    
         
            +
              # @param [Hash] properties Text to translate
         
     | 
| 
      
 80 
     | 
    
         
            +
              # @param [String, Locale] from_locale The locale of the given properties
         
     | 
| 
      
 81 
     | 
    
         
            +
              # @param [String, Locale] to_locale The target locale for translations
         
     | 
| 
      
 82 
     | 
    
         
            +
              # @return [Hash] Translated properties
         
     | 
| 
      
 83 
     | 
    
         
            +
              def translate_properties(properties, from_locale, to_locale)
         
     | 
| 
      
 84 
     | 
    
         
            +
                from_locale = parse_locale(from_locale)
         
     | 
| 
      
 85 
     | 
    
         
            +
                to_locale = parse_locale(to_locale)
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                # sanity check
         
     | 
| 
      
 88 
     | 
    
         
            +
                return properties if from_locale.language == to_locale.language
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                result = Translatomatic::TranslationResult.new(properties,
         
     | 
| 
      
 91 
     | 
    
         
            +
                  from_locale, to_locale)
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                # find translations in database first
         
     | 
| 
      
 94 
     | 
    
         
            +
                texts = find_database_translations(result)
         
     | 
| 
      
 95 
     | 
    
         
            +
                result.update_db_strings(texts)
         
     | 
| 
      
 96 
     | 
    
         
            +
                @from_db += texts.length
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                # send remaining unknown strings to translator
         
     | 
| 
      
 99 
     | 
    
         
            +
                # (copy untranslated set from result)
         
     | 
| 
      
 100 
     | 
    
         
            +
                untranslated = result.untranslated.to_a.select { |i| translatable?(i) }
         
     | 
| 
      
 101 
     | 
    
         
            +
                @from_translator += untranslated.length
         
     | 
| 
      
 102 
     | 
    
         
            +
                if !untranslated.empty? && !@dry_run
         
     | 
| 
      
 103 
     | 
    
         
            +
                  translated = @translator.translate(untranslated, from_locale, to_locale)
         
     | 
| 
      
 104 
     | 
    
         
            +
                  result.update_strings(untranslated, translated)
         
     | 
| 
      
 105 
     | 
    
         
            +
                  save_database_translations(result, untranslated, translated)
         
     | 
| 
      
 106 
     | 
    
         
            +
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                log.debug("translations from db: %d translator: %d untranslated: %d" %
         
     | 
| 
      
 109 
     | 
    
         
            +
                  [texts.length, untranslated.length, result.untranslated.length])
         
     | 
| 
      
 110 
     | 
    
         
            +
                result.properties
         
     | 
| 
      
 111 
     | 
    
         
            +
              end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
              private
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
              def translatable?(string)
         
     | 
| 
      
 116 
     | 
    
         
            +
                # don't translate numbers
         
     | 
| 
      
 117 
     | 
    
         
            +
                !string.empty? && !string.match(/^[\d,]+$/)
         
     | 
| 
      
 118 
     | 
    
         
            +
              end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
              def save_database_translations(result, untranslated, translated)
         
     | 
| 
      
 121 
     | 
    
         
            +
                ActiveRecord::Base.transaction do
         
     | 
| 
      
 122 
     | 
    
         
            +
                  from = db_locale(result.from_locale)
         
     | 
| 
      
 123 
     | 
    
         
            +
                  to = db_locale(result.to_locale)
         
     | 
| 
      
 124 
     | 
    
         
            +
                  untranslated.zip(translated).each do |t1, t2|
         
     | 
| 
      
 125 
     | 
    
         
            +
                    save_database_translation(from, to, t1, t2)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
              def save_database_translation(from_locale, to_locale, t1, t2)
         
     | 
| 
      
 131 
     | 
    
         
            +
                original_text = Translatomatic::Model::Text.find_or_create_by!(
         
     | 
| 
      
 132 
     | 
    
         
            +
                  locale: from_locale,
         
     | 
| 
      
 133 
     | 
    
         
            +
                  value: t1
         
     | 
| 
      
 134 
     | 
    
         
            +
                )
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                Translatomatic::Model::Text.find_or_create_by!(
         
     | 
| 
      
 137 
     | 
    
         
            +
                  locale: to_locale,
         
     | 
| 
      
 138 
     | 
    
         
            +
                  value: t2,
         
     | 
| 
      
 139 
     | 
    
         
            +
                  from_text: original_text,
         
     | 
| 
      
 140 
     | 
    
         
            +
                  translator: @translator.class.name.demodulize
         
     | 
| 
      
 141 
     | 
    
         
            +
                )
         
     | 
| 
      
 142 
     | 
    
         
            +
              end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
              def find_database_translations(result)
         
     | 
| 
      
 145 
     | 
    
         
            +
                from = db_locale(result.from_locale)
         
     | 
| 
      
 146 
     | 
    
         
            +
                to = db_locale(result.to_locale)
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                Translatomatic::Model::Text.where({
         
     | 
| 
      
 149 
     | 
    
         
            +
                  locale: to,
         
     | 
| 
      
 150 
     | 
    
         
            +
                  from_texts_texts: { locale_id: from, value: result.untranslated.to_a }
         
     | 
| 
      
 151 
     | 
    
         
            +
                }).joins(:from_text)
         
     | 
| 
      
 152 
     | 
    
         
            +
              end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
              def db_locale(locale)
         
     | 
| 
      
 155 
     | 
    
         
            +
                Translatomatic::Model::Locale.from_tag(locale)
         
     | 
| 
      
 156 
     | 
    
         
            +
              end
         
     | 
| 
      
 157 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Translation statistics
         
     | 
| 
      
 2 
     | 
    
         
            +
            class Translatomatic::ConverterStats
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              # @return [Number] The total number of strings translated.
         
     | 
| 
      
 5 
     | 
    
         
            +
              attr_reader :translations
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              # @return [Number] The number of translations that came from the database.
         
     | 
| 
      
 8 
     | 
    
         
            +
              attr_reader :from_db
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              # @return [Number] The number of translations that came from the translator.
         
     | 
| 
      
 11 
     | 
    
         
            +
              attr_reader :from_translator
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def initialize(from_db, from_translator)
         
     | 
| 
      
 14 
     | 
    
         
            +
                @translations = from_db + from_translator
         
     | 
| 
      
 15 
     | 
    
         
            +
                @from_db = from_db
         
     | 
| 
      
 16 
     | 
    
         
            +
                @from_translator = from_translator
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def +(other)
         
     | 
| 
      
 20 
     | 
    
         
            +
                self.class.new(@from_db + other.from_db, @from_translator + other.from_translator)
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              def to_s
         
     | 
| 
      
 24 
     | 
    
         
            +
                "Total translations: #{@translations} " +
         
     | 
| 
      
 25 
     | 
    
         
            +
                  "(#{@from_db} from database, #{@from_translator} from translator)"
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,105 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_record'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class Translatomatic::Database
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              include Translatomatic::Util
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :options
         
     | 
| 
      
 9 
     | 
    
         
            +
                private
         
     | 
| 
      
 10 
     | 
    
         
            +
                include Translatomatic::DefineOptions
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def initialize(options = {})
         
     | 
| 
      
 14 
     | 
    
         
            +
                db_config_path = db_config_path(options)
         
     | 
| 
      
 15 
     | 
    
         
            +
                dbconfig = File.read(db_config_path)
         
     | 
| 
      
 16 
     | 
    
         
            +
                dbconfig.gsub!(/\$HOME/, Dir.home)
         
     | 
| 
      
 17 
     | 
    
         
            +
                dbconfig.gsub!(/\$GEM_ROOT/, GEM_ROOT)
         
     | 
| 
      
 18 
     | 
    
         
            +
                @env = options[:database_env] || DEFAULT_ENV
         
     | 
| 
      
 19 
     | 
    
         
            +
                @db_config = YAML::load(dbconfig) || {}
         
     | 
| 
      
 20 
     | 
    
         
            +
                @env_config = @db_config
         
     | 
| 
      
 21 
     | 
    
         
            +
                raise "no environment '#{@env}' in #{db_config_path}" unless @env_config[@env]
         
     | 
| 
      
 22 
     | 
    
         
            +
                @env_config = @env_config[@env]
         
     | 
| 
      
 23 
     | 
    
         
            +
                ActiveRecord::Base.configurations = @db_config
         
     | 
| 
      
 24 
     | 
    
         
            +
                ActiveRecord::Tasks::DatabaseTasks.env = @env
         
     | 
| 
      
 25 
     | 
    
         
            +
                ActiveRecord::Tasks::DatabaseTasks.db_dir = DB_PATH
         
     | 
| 
      
 26 
     | 
    
         
            +
                ActiveRecord::Tasks::DatabaseTasks.root = DB_PATH
         
     | 
| 
      
 27 
     | 
    
         
            +
                ActiveRecord::Tasks::DatabaseTasks.database_configuration = @db_config
         
     | 
| 
      
 28 
     | 
    
         
            +
                create unless exists?
         
     | 
| 
      
 29 
     | 
    
         
            +
                migrate
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              # Connect to the database
         
     | 
| 
      
 33 
     | 
    
         
            +
              # @return [void]
         
     | 
| 
      
 34 
     | 
    
         
            +
              def connect
         
     | 
| 
      
 35 
     | 
    
         
            +
                ActiveRecord::Base.establish_connection(@env_config)
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              # Disconnect from the database
         
     | 
| 
      
 39 
     | 
    
         
            +
              # @return [void]
         
     | 
| 
      
 40 
     | 
    
         
            +
              def disconnect
         
     | 
| 
      
 41 
     | 
    
         
            +
                ActiveRecord::Base.remove_connection
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              # Test if the database exists
         
     | 
| 
      
 45 
     | 
    
         
            +
              # @return [Boolean] true if the database exists
         
     | 
| 
      
 46 
     | 
    
         
            +
              def exists?
         
     | 
| 
      
 47 
     | 
    
         
            +
                begin
         
     | 
| 
      
 48 
     | 
    
         
            +
                  connect
         
     | 
| 
      
 49 
     | 
    
         
            +
                  ActiveRecord::Base.connection.tables
         
     | 
| 
      
 50 
     | 
    
         
            +
                rescue
         
     | 
| 
      
 51 
     | 
    
         
            +
                  return false
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
                true
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
              # Run outstanding migrations against the database
         
     | 
| 
      
 57 
     | 
    
         
            +
              # @return [void]
         
     | 
| 
      
 58 
     | 
    
         
            +
              def migrate
         
     | 
| 
      
 59 
     | 
    
         
            +
                connect
         
     | 
| 
      
 60 
     | 
    
         
            +
                ActiveRecord::Migrator.migrate(MIGRATIONS_PATH)
         
     | 
| 
      
 61 
     | 
    
         
            +
                ActiveRecord::Base.clear_cache!
         
     | 
| 
      
 62 
     | 
    
         
            +
                log.debug "Database migrated."
         
     | 
| 
      
 63 
     | 
    
         
            +
              end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
              # Create the database
         
     | 
| 
      
 66 
     | 
    
         
            +
              # @return [void]
         
     | 
| 
      
 67 
     | 
    
         
            +
              def create
         
     | 
| 
      
 68 
     | 
    
         
            +
                ActiveRecord::Tasks::DatabaseTasks.create(@env_config)
         
     | 
| 
      
 69 
     | 
    
         
            +
                log.debug "Database created."
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              # Drop the database
         
     | 
| 
      
 73 
     | 
    
         
            +
              # @return [void]
         
     | 
| 
      
 74 
     | 
    
         
            +
              def drop
         
     | 
| 
      
 75 
     | 
    
         
            +
                disconnect
         
     | 
| 
      
 76 
     | 
    
         
            +
                ActiveRecord::Tasks::DatabaseTasks.drop(@env_config)
         
     | 
| 
      
 77 
     | 
    
         
            +
                log.debug "Database deleted."
         
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
              private
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
              DB_PATH = File.join(File.dirname(__FILE__), "..", "..", "db")
         
     | 
| 
      
 83 
     | 
    
         
            +
              INTERNAL_DB_CONFIG = File.join(DB_PATH, "database.yml")
         
     | 
| 
      
 84 
     | 
    
         
            +
              CUSTOM_DB_CONFIG = File.join(Dir.home, ".translatomatic", "database.yml")
         
     | 
| 
      
 85 
     | 
    
         
            +
              DEFAULT_DB_CONFIG = File.exist?(CUSTOM_DB_CONFIG) ? CUSTOM_DB_CONFIG : INTERNAL_DB_CONFIG
         
     | 
| 
      
 86 
     | 
    
         
            +
              MIGRATIONS_PATH = File.join(DB_PATH, "migrate")
         
     | 
| 
      
 87 
     | 
    
         
            +
              GEM_ROOT = File.join(File.dirname(__FILE__), "..", "..")
         
     | 
| 
      
 88 
     | 
    
         
            +
              DEFAULT_ENV = "production"
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
              define_options(
         
     | 
| 
      
 91 
     | 
    
         
            +
                { name: :database_config, description: "Database config file",
         
     | 
| 
      
 92 
     | 
    
         
            +
                  default: DEFAULT_DB_CONFIG },
         
     | 
| 
      
 93 
     | 
    
         
            +
                { name: :database_env, description: "Database environment",
         
     | 
| 
      
 94 
     | 
    
         
            +
                  default: DEFAULT_ENV })
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
              def db_config_path(options)
         
     | 
| 
      
 97 
     | 
    
         
            +
                if options[:database_env] == "test"
         
     | 
| 
      
 98 
     | 
    
         
            +
                  INTERNAL_DB_CONFIG  # rspec
         
     | 
| 
      
 99 
     | 
    
         
            +
                elsif options[:database_config]
         
     | 
| 
      
 100 
     | 
    
         
            +
                  return options[:database_config]
         
     | 
| 
      
 101 
     | 
    
         
            +
                else
         
     | 
| 
      
 102 
     | 
    
         
            +
                  DEFAULT_DB_CONFIG
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
              end
         
     | 
| 
      
 105 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,90 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Module to encode and decode unicode chars.
         
     | 
| 
      
 2 
     | 
    
         
            +
            # This code is highly influced by Florian Frank's JSON gem
         
     | 
| 
      
 3 
     | 
    
         
            +
            # @see https://github.com/jnbt/java-properties
         
     | 
| 
      
 4 
     | 
    
         
            +
            # @see https://github.com/flori/json/
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Translatomatic::EscapedUnicode
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              # Decodes all unicode chars from escape sequences
         
     | 
| 
      
 9 
     | 
    
         
            +
              # @param text [String]
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @return [String] The encoded text for chaining
         
     | 
| 
      
 11 
     | 
    
         
            +
              def self.unescape(text)
         
     | 
| 
      
 12 
     | 
    
         
            +
                string = text.dup
         
     | 
| 
      
 13 
     | 
    
         
            +
                string = string.gsub(%r((?:\\[uU](?:[A-Fa-f\d]{4}))+)) do |c|
         
     | 
| 
      
 14 
     | 
    
         
            +
                  c.downcase!
         
     | 
| 
      
 15 
     | 
    
         
            +
                  bytes = EMPTY_8BIT_STRING.dup
         
     | 
| 
      
 16 
     | 
    
         
            +
                  i = 0
         
     | 
| 
      
 17 
     | 
    
         
            +
                  while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
         
     | 
| 
      
 18 
     | 
    
         
            +
                    bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    i += 1
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                  bytes.encode("utf-8", "utf-16be")
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
                string.force_encoding(::Encoding::UTF_8)
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                text.replace string
         
     | 
| 
      
 26 
     | 
    
         
            +
                text
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              # Decodes all unicode chars into escape sequences
         
     | 
| 
      
 30 
     | 
    
         
            +
              # @param text [String]
         
     | 
| 
      
 31 
     | 
    
         
            +
              # @return [String] The decoded text for chaining
         
     | 
| 
      
 32 
     | 
    
         
            +
              def self.escape(text)
         
     | 
| 
      
 33 
     | 
    
         
            +
                string = text.dup
         
     | 
| 
      
 34 
     | 
    
         
            +
                string.force_encoding(::Encoding::ASCII_8BIT)
         
     | 
| 
      
 35 
     | 
    
         
            +
                string.gsub!(/["\\\x0-\x1f]/n) { |c| MAP[c] || c }
         
     | 
| 
      
 36 
     | 
    
         
            +
                string.gsub!(/(
         
     | 
| 
      
 37 
     | 
    
         
            +
                  (?:
         
     | 
| 
      
 38 
     | 
    
         
            +
                    [\xc2-\xdf][\x80-\xbf]    |
         
     | 
| 
      
 39 
     | 
    
         
            +
                    [\xe0-\xef][\x80-\xbf]{2} |
         
     | 
| 
      
 40 
     | 
    
         
            +
                    [\xf0-\xf4][\x80-\xbf]{3}
         
     | 
| 
      
 41 
     | 
    
         
            +
                    )+ |
         
     | 
| 
      
 42 
     | 
    
         
            +
                    [\x80-\xc1\xf5-\xff]       # invalid
         
     | 
| 
      
 43 
     | 
    
         
            +
                    )/nx) { |c|
         
     | 
| 
      
 44 
     | 
    
         
            +
                      c.size == 1 and raise "Invalid utf8 byte: '#{c}'"
         
     | 
| 
      
 45 
     | 
    
         
            +
                      s = c.encode("utf-16be", "utf-8").unpack('H*')[0]
         
     | 
| 
      
 46 
     | 
    
         
            +
                      s.force_encoding(::Encoding::ASCII_8BIT)
         
     | 
| 
      
 47 
     | 
    
         
            +
                      s.gsub!(/.{4}/n, '\\\\u\&')
         
     | 
| 
      
 48 
     | 
    
         
            +
                      s.force_encoding(::Encoding::UTF_8)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    }
         
     | 
| 
      
 50 
     | 
    
         
            +
                string.force_encoding(::Encoding::UTF_8)
         
     | 
| 
      
 51 
     | 
    
         
            +
                text.replace string
         
     | 
| 
      
 52 
     | 
    
         
            +
                text
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
              private
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
              MAP = {
         
     | 
| 
      
 58 
     | 
    
         
            +
                "\x0" => '\u0000',
         
     | 
| 
      
 59 
     | 
    
         
            +
                "\x1" => '\u0001',
         
     | 
| 
      
 60 
     | 
    
         
            +
                "\x2" => '\u0002',
         
     | 
| 
      
 61 
     | 
    
         
            +
                "\x3" => '\u0003',
         
     | 
| 
      
 62 
     | 
    
         
            +
                "\x4" => '\u0004',
         
     | 
| 
      
 63 
     | 
    
         
            +
                "\x5" => '\u0005',
         
     | 
| 
      
 64 
     | 
    
         
            +
                "\x6" => '\u0006',
         
     | 
| 
      
 65 
     | 
    
         
            +
                "\x7" => '\u0007',
         
     | 
| 
      
 66 
     | 
    
         
            +
                "\xb" => '\u000b',
         
     | 
| 
      
 67 
     | 
    
         
            +
                "\xe" => '\u000e',
         
     | 
| 
      
 68 
     | 
    
         
            +
                "\xf" => '\u000f',
         
     | 
| 
      
 69 
     | 
    
         
            +
                "\x10" => '\u0010',
         
     | 
| 
      
 70 
     | 
    
         
            +
                "\x11" => '\u0011',
         
     | 
| 
      
 71 
     | 
    
         
            +
                "\x12" => '\u0012',
         
     | 
| 
      
 72 
     | 
    
         
            +
                "\x13" => '\u0013',
         
     | 
| 
      
 73 
     | 
    
         
            +
                "\x14" => '\u0014',
         
     | 
| 
      
 74 
     | 
    
         
            +
                "\x15" => '\u0015',
         
     | 
| 
      
 75 
     | 
    
         
            +
                "\x16" => '\u0016',
         
     | 
| 
      
 76 
     | 
    
         
            +
                "\x17" => '\u0017',
         
     | 
| 
      
 77 
     | 
    
         
            +
                "\x18" => '\u0018',
         
     | 
| 
      
 78 
     | 
    
         
            +
                "\x19" => '\u0019',
         
     | 
| 
      
 79 
     | 
    
         
            +
                "\x1a" => '\u001a',
         
     | 
| 
      
 80 
     | 
    
         
            +
                "\x1b" => '\u001b',
         
     | 
| 
      
 81 
     | 
    
         
            +
                "\x1c" => '\u001c',
         
     | 
| 
      
 82 
     | 
    
         
            +
                "\x1d" => '\u001d',
         
     | 
| 
      
 83 
     | 
    
         
            +
                "\x1e" => '\u001e',
         
     | 
| 
      
 84 
     | 
    
         
            +
                "\x1f" => '\u001f',
         
     | 
| 
      
 85 
     | 
    
         
            +
              }
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
              EMPTY_8BIT_STRING = ''
         
     | 
| 
      
 88 
     | 
    
         
            +
              EMPTY_8BIT_STRING.force_encoding(::Encoding::ASCII_8BIT)
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Translatomatic
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Model
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Locale < ActiveRecord::Base
         
     | 
| 
      
 4 
     | 
    
         
            +
                  has_many :texts, class_name: "Translatomatic::Model::Text"
         
     | 
| 
      
 5 
     | 
    
         
            +
                  validates_presence_of :language
         
     | 
| 
      
 6 
     | 
    
         
            +
                  validates_uniqueness_of :language, scope: [:script, :region]
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 9 
     | 
    
         
            +
                    include Translatomatic::Util
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  # create a locale record from an I18n::Locale::Tag object or string
         
     | 
| 
      
 13 
     | 
    
         
            +
                  def self.from_tag(tag)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    tag = parse_locale(tag) if tag.kind_of?(String)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    find_or_create_by!({
         
     | 
| 
      
 16 
     | 
    
         
            +
                      language: tag.language, script: tag.script, region: tag.region
         
     | 
| 
      
 17 
     | 
    
         
            +
                    })
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Translatomatic
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Model
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Text < ActiveRecord::Base
         
     | 
| 
      
 4 
     | 
    
         
            +
                  belongs_to :locale, class_name: "Translatomatic::Model::Locale"
         
     | 
| 
      
 5 
     | 
    
         
            +
                  belongs_to :from_text, class_name: "Translatomatic::Model::Text"
         
     | 
| 
      
 6 
     | 
    
         
            +
                  has_many :translations, class_name: "Translatomatic::Model::Text",
         
     | 
| 
      
 7 
     | 
    
         
            +
                    foreign_key: :from_text_id, dependent: :delete_all
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  validates_presence_of :value
         
     | 
| 
      
 10 
     | 
    
         
            +
                  validates_presence_of :locale
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Translatomatic
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Option
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :name, :required, :use_env, :description
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(data = {})
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @name = data[:name]
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @required = data[:required]
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @use_env = data[:use_env]
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @description = data[:desc]
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @data = data
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def to_hash
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @data
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              module DefineOptions
         
     | 
| 
      
 19 
     | 
    
         
            +
                private
         
     | 
| 
      
 20 
     | 
    
         
            +
                def define_options(*options)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @options = options.collect { |i| Translatomatic::Option.new(i) }
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,137 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @abstract Subclasses implement different types of resource files
         
     | 
| 
      
 2 
     | 
    
         
            +
            class Translatomatic::ResourceFile::Base
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              attr_accessor :locale
         
     | 
| 
      
 5 
     | 
    
         
            +
              attr_accessor :path
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              # @return [Hash<String,String>] key -> value properties
         
     | 
| 
      
 8 
     | 
    
         
            +
              attr_reader :properties
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              # Create a new resource file.
         
     | 
| 
      
 11 
     | 
    
         
            +
              # If locale is unspecified, attempts to determine the locale of the file
         
     | 
| 
      
 12 
     | 
    
         
            +
              # automatically, and if that fails, uses the default locale.
         
     | 
| 
      
 13 
     | 
    
         
            +
              # @param [String] path Path to the file
         
     | 
| 
      
 14 
     | 
    
         
            +
              # @param [String] locale Locale of the file contents
         
     | 
| 
      
 15 
     | 
    
         
            +
              # @return [Translatomatic::ResourceFile::Base] the resource file.
         
     | 
| 
      
 16 
     | 
    
         
            +
              def initialize(path, locale = nil)
         
     | 
| 
      
 17 
     | 
    
         
            +
                @path = path.kind_of?(Pathname) ? path : Pathname.new(path)
         
     | 
| 
      
 18 
     | 
    
         
            +
                @locale = locale || detect_locale || parse_locale(I18n.default_locale)
         
     | 
| 
      
 19 
     | 
    
         
            +
                raise "unable to determine locale" unless @locale && @locale.language
         
     | 
| 
      
 20 
     | 
    
         
            +
                @valid = false
         
     | 
| 
      
 21 
     | 
    
         
            +
                @properties = {}
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              def format
         
     | 
| 
      
 25 
     | 
    
         
            +
                self.class.name.demodulize.downcase.to_sym
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              # Create a path for the current resource file with a given locale
         
     | 
| 
      
 29 
     | 
    
         
            +
              # @param [String] locale for the path
         
     | 
| 
      
 30 
     | 
    
         
            +
              # @return [Pathname] The path of this resource file modified for the given locale
         
     | 
| 
      
 31 
     | 
    
         
            +
              def locale_path(locale)
         
     | 
| 
      
 32 
     | 
    
         
            +
                basename = path.sub_ext('').basename.to_s
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                extlist = extension_list
         
     | 
| 
      
 35 
     | 
    
         
            +
                if extlist.length >= 2 && loc_idx = find_locale(extlist)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  extlist[loc_idx] = locale.to_s
         
     | 
| 
      
 37 
     | 
    
         
            +
                elsif valid_locale?(basename)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  path.dirname + (locale.to_s + path.extname)
         
     | 
| 
      
 39 
     | 
    
         
            +
                else
         
     | 
| 
      
 40 
     | 
    
         
            +
                  deunderscored = basename.sub(/_.*?$/, '')
         
     | 
| 
      
 41 
     | 
    
         
            +
                  filename = deunderscored + "_" + locale.to_s + path.extname
         
     | 
| 
      
 42 
     | 
    
         
            +
                  path.dirname + filename
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
              # Set all properties
         
     | 
| 
      
 47 
     | 
    
         
            +
              # @param [Hash<String,String>] properties New properties
         
     | 
| 
      
 48 
     | 
    
         
            +
              def properties=(properties)
         
     | 
| 
      
 49 
     | 
    
         
            +
                # use set rather that set @properties directly as subclasses override set()
         
     | 
| 
      
 50 
     | 
    
         
            +
                properties.each do |key, value|
         
     | 
| 
      
 51 
     | 
    
         
            +
                  set(key, value)
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
              # Get the value of a property
         
     | 
| 
      
 56 
     | 
    
         
            +
              # @param [String] name The name of the property
         
     | 
| 
      
 57 
     | 
    
         
            +
              # @return [String] The value of the property
         
     | 
| 
      
 58 
     | 
    
         
            +
              def get(name)
         
     | 
| 
      
 59 
     | 
    
         
            +
                @properties[name]
         
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
              # Set a property
         
     | 
| 
      
 63 
     | 
    
         
            +
              # @param [String] key The name of the property
         
     | 
| 
      
 64 
     | 
    
         
            +
              # @param [String] value The new value of the property
         
     | 
| 
      
 65 
     | 
    
         
            +
              # @return [String] The new value of the property
         
     | 
| 
      
 66 
     | 
    
         
            +
              def set(name, value)
         
     | 
| 
      
 67 
     | 
    
         
            +
                @properties[name] = value
         
     | 
| 
      
 68 
     | 
    
         
            +
              end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
              # Test if the current resource file is valid
         
     | 
| 
      
 71 
     | 
    
         
            +
              # @return true if the current file is valid
         
     | 
| 
      
 72 
     | 
    
         
            +
              def valid?
         
     | 
| 
      
 73 
     | 
    
         
            +
                @valid
         
     | 
| 
      
 74 
     | 
    
         
            +
              end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              # Save the resource file.
         
     | 
| 
      
 77 
     | 
    
         
            +
              # @param [Pathname] target The destination path
         
     | 
| 
      
 78 
     | 
    
         
            +
              # @return [void]
         
     | 
| 
      
 79 
     | 
    
         
            +
              def save(target = path)
         
     | 
| 
      
 80 
     | 
    
         
            +
                raise "save(path) must be implemented by subclass"
         
     | 
| 
      
 81 
     | 
    
         
            +
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
              # @return [String] String representation of this file
         
     | 
| 
      
 84 
     | 
    
         
            +
              def to_s
         
     | 
| 
      
 85 
     | 
    
         
            +
                "#{path.basename.to_s} (#{locale})"
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
              private
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
              include Translatomatic::Util
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              # detect locale from filename
         
     | 
| 
      
 93 
     | 
    
         
            +
              def detect_locale
         
     | 
| 
      
 94 
     | 
    
         
            +
                tag = nil
         
     | 
| 
      
 95 
     | 
    
         
            +
                basename = path.sub_ext('').basename.to_s
         
     | 
| 
      
 96 
     | 
    
         
            +
                directory = path.dirname.basename.to_s
         
     | 
| 
      
 97 
     | 
    
         
            +
                extlist = extension_list
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                if basename.match(/_([-\w]{2,})$/i)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  # locale after underscore in filename
         
     | 
| 
      
 101 
     | 
    
         
            +
                  tag = $1
         
     | 
| 
      
 102 
     | 
    
         
            +
                elsif directory.match(/^([-\w]+)\.lproj$/)
         
     | 
| 
      
 103 
     | 
    
         
            +
                  # xcode localized strings
         
     | 
| 
      
 104 
     | 
    
         
            +
                  tag = $1
         
     | 
| 
      
 105 
     | 
    
         
            +
                elsif extlist.length >= 2 && loc_idx = find_locale(extlist)
         
     | 
| 
      
 106 
     | 
    
         
            +
                  # multiple parts to extension, e.g. index.html.en
         
     | 
| 
      
 107 
     | 
    
         
            +
                  tag = extlist[loc_idx]
         
     | 
| 
      
 108 
     | 
    
         
            +
                elsif valid_locale?(basename)
         
     | 
| 
      
 109 
     | 
    
         
            +
                  # try to match on entire basename
         
     | 
| 
      
 110 
     | 
    
         
            +
                  # (support for rails en.yml)
         
     | 
| 
      
 111 
     | 
    
         
            +
                  tag = basename
         
     | 
| 
      
 112 
     | 
    
         
            +
                end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                tag ? parse_locale(tag, true) : nil
         
     | 
| 
      
 115 
     | 
    
         
            +
              end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
              # test if the list of strings contains a valid locale
         
     | 
| 
      
 118 
     | 
    
         
            +
              # return the index to the locale, or nil if no locales found
         
     | 
| 
      
 119 
     | 
    
         
            +
              def find_locale(list)
         
     | 
| 
      
 120 
     | 
    
         
            +
                list.find_index { |i| valid_locale?(i) }
         
     | 
| 
      
 121 
     | 
    
         
            +
              end
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
              # ext_sub() only removes the last extension
         
     | 
| 
      
 124 
     | 
    
         
            +
              def strip_extensions
         
     | 
| 
      
 125 
     | 
    
         
            +
                filename = path.basename.to_s
         
     | 
| 
      
 126 
     | 
    
         
            +
                filename.sub!(/\..*$/, '')
         
     | 
| 
      
 127 
     | 
    
         
            +
                path.parent + filename
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
              # for index.html.de, returns ['html', 'de']
         
     | 
| 
      
 131 
     | 
    
         
            +
              def extension_list
         
     | 
| 
      
 132 
     | 
    
         
            +
                filename = path.basename.to_s
         
     | 
| 
      
 133 
     | 
    
         
            +
                idx = filename.index('.')
         
     | 
| 
      
 134 
     | 
    
         
            +
                idx && idx < filename.length - 1 ? filename[idx + 1..-1].split('.') : []
         
     | 
| 
      
 135 
     | 
    
         
            +
              end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Translatomatic::ResourceFile
         
     | 
| 
      
 2 
     | 
    
         
            +
              class HTML < XML
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                def self.extensions
         
     | 
| 
      
 5 
     | 
    
         
            +
                  %w{html htm shtml}
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # (see Translatomatic::ResourceFile::Base#locale_path)
         
     | 
| 
      
 9 
     | 
    
         
            +
                def locale_path(locale)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  extlist = extension_list
         
     | 
| 
      
 11 
     | 
    
         
            +
                  if extlist.length >= 2 && loc_idx = find_locale(extlist)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    # part of the extension is the locale
         
     | 
| 
      
 13 
     | 
    
         
            +
                    # replace that part with the new locale
         
     | 
| 
      
 14 
     | 
    
         
            +
                    extlist[loc_idx] = locale.to_s
         
     | 
| 
      
 15 
     | 
    
         
            +
                    new_extension = extlist.join(".")
         
     | 
| 
      
 16 
     | 
    
         
            +
                    return strip_extensions.sub_ext("." + new_extension)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  else
         
     | 
| 
      
 18 
     | 
    
         
            +
                    # add locale extension
         
     | 
| 
      
 19 
     | 
    
         
            +
                    ext = path.extname
         
     | 
| 
      
 20 
     | 
    
         
            +
                    path.sub_ext("#{ext}." + locale.to_s)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  # fall back to base functionality
         
     | 
| 
      
 24 
     | 
    
         
            +
                  #super(locale)
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                # (see Translatomatic::ResourceFile::Base#save(path))
         
     | 
| 
      
 28 
     | 
    
         
            +
                def save(target = path)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  target.write(@doc.to_html) if @doc
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,29 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Translatomatic::ResourceFile
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Plist < XML
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                def self.extensions
         
     | 
| 
      
 5 
     | 
    
         
            +
                  %w{plist}
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # (see Translatomatic::ResourceFile::Base#locale_path)
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @note localization files in XCode use the following file name
         
     | 
| 
      
 10 
     | 
    
         
            +
                #   convention: Project/locale.lproj/filename
         
     | 
| 
      
 11 
     | 
    
         
            +
                # @todo refactor this and xcode_strings.rb to use the same code
         
     | 
| 
      
 12 
     | 
    
         
            +
                def locale_path(locale)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  if path.to_s.match(/\/([-\w]+).lproj\/.+.plist$/)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    # xcode style
         
     | 
| 
      
 15 
     | 
    
         
            +
                    filename = path.basename
         
     | 
| 
      
 16 
     | 
    
         
            +
                    path.parent.parent + (locale.to_s + ".lproj") + filename
         
     | 
| 
      
 17 
     | 
    
         
            +
                  else
         
     | 
| 
      
 18 
     | 
    
         
            +
                    super(locale)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                private
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def text_nodes_xpath
         
     | 
| 
      
 25 
     | 
    
         
            +
                  '//*[not(self::key)]/text()'
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              end # class
         
     | 
| 
      
 29 
     | 
    
         
            +
            end   # module
         
     |