translatomatic 0.1.3 → 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.
- checksums.yaml +5 -5
- data/.gitattributes +20 -20
- data/.gitignore +19 -15
- data/.rspec +3 -3
- data/.rubocop.yml +28 -0
- data/.translatomatic/config.yml +4 -0
- data/.travis.yml +4 -6
- data/.yardopts +9 -9
- data/Gemfile +8 -4
- data/Guardfile +4 -5
- data/README.de.md +55 -50
- data/README.en.md +177 -0
- data/README.es.md +53 -48
- data/README.fr.md +53 -48
- data/README.it.md +54 -49
- data/README.ja.md +63 -58
- data/README.ko.md +59 -54
- data/README.md +17 -13
- data/README.ms.md +50 -45
- data/README.pt.md +54 -49
- data/README.ru.md +57 -52
- data/README.sv.md +51 -46
- data/README.zh.md +60 -55
- data/Rakefile +3 -3
- data/TODO.txt +6 -0
- data/bin/console +3 -3
- data/bin/translatomatic +4 -2
- data/config/i18n-tasks.yml +130 -0
- data/config/locales/translatomatic/de.yml +141 -99
- data/config/locales/translatomatic/en.yml +129 -89
- data/config/locales/translatomatic/es.yml +136 -99
- data/config/locales/translatomatic/fr.yml +139 -100
- data/config/locales/translatomatic/it.yml +135 -97
- data/config/locales/translatomatic/ja.yml +137 -98
- data/config/locales/translatomatic/ko.yml +138 -98
- data/config/locales/translatomatic/ms.yml +138 -100
- data/config/locales/translatomatic/pt.yml +137 -101
- data/config/locales/translatomatic/ru.yml +136 -98
- data/config/locales/translatomatic/sv.yml +134 -96
- data/config/locales/translatomatic/zh.yml +136 -97
- data/db/migrate/201712170000_initial.rb +2 -3
- data/lib/translatomatic.rb +40 -25
- data/lib/translatomatic/cli.rb +5 -1
- data/lib/translatomatic/cli/base.rb +61 -58
- data/lib/translatomatic/cli/common_options.rb +14 -11
- data/lib/translatomatic/cli/config.rb +96 -91
- data/lib/translatomatic/cli/database.rb +85 -23
- data/lib/translatomatic/cli/main.rb +158 -104
- data/lib/translatomatic/cli/thor.rb +29 -0
- data/lib/translatomatic/cli/translate.rb +134 -157
- data/lib/translatomatic/config.rb +10 -301
- data/lib/translatomatic/config/display.rb +78 -0
- data/lib/translatomatic/config/files.rb +60 -0
- data/lib/translatomatic/config/location_settings.rb +133 -0
- data/lib/translatomatic/config/options.rb +68 -0
- data/lib/translatomatic/config/selector.rb +127 -0
- data/lib/translatomatic/config/settings.rb +148 -0
- data/lib/translatomatic/converter.rb +40 -28
- data/lib/translatomatic/database.rb +127 -110
- data/lib/translatomatic/define_options.rb +4 -5
- data/lib/translatomatic/escaped_unicode.rb +86 -76
- data/lib/translatomatic/extractor.rb +5 -2
- data/lib/translatomatic/extractor/base.rb +12 -12
- data/lib/translatomatic/extractor/ruby.rb +7 -6
- data/lib/translatomatic/file_translator.rb +101 -244
- data/lib/translatomatic/flattenation.rb +39 -0
- data/lib/translatomatic/http.rb +13 -0
- data/lib/translatomatic/http/client.rb +144 -0
- data/lib/translatomatic/http/exception.rb +43 -0
- data/lib/translatomatic/http/file_param.rb +27 -0
- data/lib/translatomatic/http/param.rb +37 -0
- data/lib/translatomatic/http/request.rb +91 -0
- data/lib/translatomatic/i18n.rb +43 -0
- data/lib/translatomatic/locale.rb +71 -59
- data/lib/translatomatic/logger.rb +43 -28
- data/lib/translatomatic/metadata.rb +58 -0
- data/lib/translatomatic/model.rb +4 -2
- data/lib/translatomatic/model/locale.rb +5 -5
- data/lib/translatomatic/model/text.rb +5 -5
- data/lib/translatomatic/option.rb +57 -34
- data/lib/translatomatic/path_utils.rb +126 -0
- data/lib/translatomatic/progress_updater.rb +13 -16
- data/lib/translatomatic/provider.rb +101 -0
- data/lib/translatomatic/provider/base.rb +136 -0
- data/lib/translatomatic/provider/frengly.rb +55 -0
- data/lib/translatomatic/provider/google.rb +78 -0
- data/lib/translatomatic/provider/google_web.rb +50 -0
- data/lib/translatomatic/provider/microsoft.rb +144 -0
- data/lib/translatomatic/provider/my_memory.rb +75 -0
- data/lib/translatomatic/provider/yandex.rb +61 -0
- data/lib/translatomatic/resource_file.rb +59 -53
- data/lib/translatomatic/resource_file/base.rb +171 -237
- data/lib/translatomatic/resource_file/csv.rb +176 -24
- data/lib/translatomatic/resource_file/html.rb +21 -42
- data/lib/translatomatic/resource_file/key_value_support.rb +117 -0
- data/lib/translatomatic/resource_file/markdown.rb +36 -38
- data/lib/translatomatic/resource_file/plist.rb +121 -126
- data/lib/translatomatic/resource_file/po.rb +104 -82
- data/lib/translatomatic/resource_file/properties.rb +48 -77
- data/lib/translatomatic/resource_file/properties.treetop +87 -0
- data/lib/translatomatic/resource_file/resw.rb +56 -41
- data/lib/translatomatic/resource_file/subtitle.rb +86 -54
- data/lib/translatomatic/resource_file/text.rb +18 -18
- data/lib/translatomatic/resource_file/xcode_strings.rb +32 -63
- data/lib/translatomatic/resource_file/xcode_strings.treetop +85 -0
- data/lib/translatomatic/resource_file/xml.rb +94 -81
- data/lib/translatomatic/resource_file/yaml.rb +54 -68
- data/lib/translatomatic/retry_executor.rb +37 -0
- data/lib/translatomatic/slurp.rb +32 -0
- data/lib/translatomatic/string_batcher.rb +50 -0
- data/lib/translatomatic/string_escaping.rb +61 -0
- data/lib/translatomatic/text.rb +263 -0
- data/lib/translatomatic/text_collection.rb +66 -0
- data/lib/translatomatic/tmx.rb +5 -3
- data/lib/translatomatic/tmx/document.rb +107 -82
- data/lib/translatomatic/tmx/translation_unit.rb +19 -18
- data/lib/translatomatic/translation.rb +8 -28
- data/lib/translatomatic/translation/collection.rb +199 -0
- data/lib/translatomatic/translation/fetcher.rb +123 -0
- data/lib/translatomatic/translation/munging.rb +112 -0
- data/lib/translatomatic/translation/result.rb +50 -0
- data/lib/translatomatic/translation/sharer.rb +32 -0
- data/lib/translatomatic/translation/stats.rb +44 -0
- data/lib/translatomatic/translator.rb +91 -88
- data/lib/translatomatic/type_cast.rb +63 -0
- data/lib/translatomatic/util.rb +37 -33
- data/lib/translatomatic/version.rb +2 -2
- data/translatomatic.gemspec +57 -46
- metadata +136 -59
- data/lib/translatomatic/http_request.rb +0 -162
- data/lib/translatomatic/string.rb +0 -188
- data/lib/translatomatic/translation_result.rb +0 -86
- data/lib/translatomatic/translation_stats.rb +0 -31
- data/lib/translatomatic/translator/base.rb +0 -128
- data/lib/translatomatic/translator/frengly.rb +0 -62
- data/lib/translatomatic/translator/google.rb +0 -37
- data/lib/translatomatic/translator/microsoft.rb +0 -41
- data/lib/translatomatic/translator/my_memory.rb +0 -68
- data/lib/translatomatic/translator/yandex.rb +0 -56
@@ -0,0 +1,75 @@
|
|
1
|
+
module Translatomatic
|
2
|
+
module Provider
|
3
|
+
# Interface to the MyMemory translation API
|
4
|
+
# @see https://mymemory.translated.net/doc/
|
5
|
+
class MyMemory < Base
|
6
|
+
define_option :mymemory_api_key, use_env: true,
|
7
|
+
desc: t('provider.mymemory.api_key')
|
8
|
+
define_option :mymemory_email, use_env: true,
|
9
|
+
desc: t('provider.email_address')
|
10
|
+
|
11
|
+
# Create a new MyMemory provider instance
|
12
|
+
def initialize(options = {})
|
13
|
+
super(options)
|
14
|
+
@key = options[:mymemory_api_key] || ENV['MYMEMORY_API_KEY']
|
15
|
+
@email = options[:mymemory_email] || ENV['MYMEMORY_EMAIL']
|
16
|
+
@query_options = {}
|
17
|
+
@query_options[:de] = @email if @email
|
18
|
+
@query_options.merge!(key: @key) if @key
|
19
|
+
end
|
20
|
+
|
21
|
+
# (see Base#languages)
|
22
|
+
def languages
|
23
|
+
Locale.language_codes
|
24
|
+
end
|
25
|
+
|
26
|
+
# Upload a set of translations to MyMemory
|
27
|
+
# @param tmx [Translatomatic::TMX::Document] TMX document
|
28
|
+
# @return [void]
|
29
|
+
def upload(tmx)
|
30
|
+
body = [
|
31
|
+
{ key: :tmx, filename: 'import.tmx',
|
32
|
+
content: tmx.to_xml, mime_type: 'application/xml' },
|
33
|
+
{ key: :private, value: 0 }
|
34
|
+
]
|
35
|
+
response = http_client.post(UPLOAD_URL, body)
|
36
|
+
log.debug(t('provider.share_response',
|
37
|
+
response: response.body.inspect))
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
GET_URL = 'https://api.mymemory.translated.net/get'.freeze
|
43
|
+
UPLOAD_URL = 'https://api.mymemory.translated.net/tmx/import'.freeze
|
44
|
+
MAIN_URL = 'https://mymemory.api.net'.freeze
|
45
|
+
|
46
|
+
def perform_translate(strings, from, to)
|
47
|
+
perform_fetch_translations(GET_URL, strings, from, to)
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch_translations(string, from, to)
|
51
|
+
response = http_client.get(GET_URL, {
|
52
|
+
langpair: from.to_s + '|' + to.to_s,
|
53
|
+
q: string # multiple q strings not supported (tested 20180101)
|
54
|
+
}.merge(@query_options))
|
55
|
+
|
56
|
+
log.debug("#{name} response: #{response.body}")
|
57
|
+
data = JSON.parse(response.body)
|
58
|
+
# matches = data['matches'] # all translations
|
59
|
+
# matches.collect { |i| match_data(i) }
|
60
|
+
result = try_hash(data, 'responseData', 'translatedText')
|
61
|
+
add_translations(string, result)
|
62
|
+
end
|
63
|
+
|
64
|
+
# https://mymemory.translated.net/doc/features.php
|
65
|
+
def match_data(match)
|
66
|
+
{
|
67
|
+
translation: match['translation'],
|
68
|
+
quality: match['quality'],
|
69
|
+
usage_count: match['usage-count'],
|
70
|
+
match: match['match'], # partial matches, see features.php above
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Translatomatic
|
2
|
+
module Provider
|
3
|
+
# Interface to the Yandex translation API
|
4
|
+
# @see https://tech.yandex.com/translate/
|
5
|
+
class Yandex < Base
|
6
|
+
define_option :yandex_api_key, use_env: true,
|
7
|
+
desc: t('provider.yandex.api_key')
|
8
|
+
|
9
|
+
# Create a new Yandex provider instance
|
10
|
+
def initialize(options = {})
|
11
|
+
super(options)
|
12
|
+
@api_key = options[:yandex_api_key] || ENV['YANDEX_API_KEY']
|
13
|
+
raise t('provider.yandex.key_required') if @api_key.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
# (see Base#languages)
|
17
|
+
def languages
|
18
|
+
@languages ||= begin
|
19
|
+
response = http_client.post(LANGUAGES_URL, key: @api_key, ui: 'en')
|
20
|
+
data = JSON.parse(response.body) || {}
|
21
|
+
langs = data['langs'] || {}
|
22
|
+
langs.keys.flatten.uniq
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
BASE_URL = 'https://translate.yandex.net/api/v1.5/tr.json'.freeze
|
29
|
+
TRANSLATE_URL = (BASE_URL + '/translate').freeze
|
30
|
+
LANGUAGES_URL = (BASE_URL + '/getLangs').freeze
|
31
|
+
LIMIT = [nil, 10_000].freeze # strings, characters per request
|
32
|
+
|
33
|
+
def perform_translate(strings, from, to)
|
34
|
+
batcher(strings, max_count: LIMIT[0], max_length: LIMIT[1])
|
35
|
+
.each_batch do |texts|
|
36
|
+
fetch_translations(texts, from, to)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch_translations(strings, from, to)
|
41
|
+
body = request_body(strings, from, to)
|
42
|
+
response = http_client.post(TRANSLATE_URL, body)
|
43
|
+
log.debug("#{name} response: #{response.body}")
|
44
|
+
data = JSON.parse(response.body)
|
45
|
+
result = data['text'] || []
|
46
|
+
strings.zip(result).each do |original, translated|
|
47
|
+
add_translations(original, translated)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def request_body(strings, from, to)
|
52
|
+
{
|
53
|
+
key: @api_key,
|
54
|
+
text: strings,
|
55
|
+
lang: from.language + '-' + to.language,
|
56
|
+
format: 'plain' # 'html'
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -4,76 +4,81 @@ module Translatomatic
|
|
4
4
|
module ResourceFile
|
5
5
|
class << self
|
6
6
|
include Translatomatic::Util
|
7
|
-
end
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
8
|
+
# Load a resource file. If options[:locale] is not specified,
|
9
|
+
# the locale of the file will be determined from the filename,
|
10
|
+
# or else the current default locale will be used.
|
11
|
+
# @param path [String] Path to the resource file
|
12
|
+
# @return [Translatomatic::ResourceFile::Base] The resource file, or nil
|
13
|
+
# if the file type is unsupported.
|
14
|
+
def load(path, options = {})
|
15
|
+
path = path.is_a?(Pathname) ? path : Pathname.new(path)
|
16
|
+
types_for_path(path).each do |klass|
|
17
|
+
log.debug(t('file.loading', file: path, name: klass.name.demodulize))
|
18
|
+
return klass.new(path, options)
|
19
|
+
end
|
20
|
+
nil
|
21
21
|
end
|
22
|
-
nil
|
23
|
-
end
|
24
22
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
# Create a new resource file
|
24
|
+
def create(path, options = {})
|
25
|
+
klass = types_for_path(path).first
|
26
|
+
return nil unless klass
|
27
|
+
file = klass.new
|
28
|
+
file.path = path
|
29
|
+
file.locale = build_locale(options[:locale])
|
30
|
+
file
|
31
|
+
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
33
|
+
# Find all resource files under the given directory. Follows symlinks.
|
34
|
+
# @param path [String, Pathname] The path to search from
|
35
|
+
# @return [Array<Translatomatic::ResourceFile>] Resource files found
|
36
|
+
def find(path, options = {})
|
37
|
+
files = []
|
38
|
+
include_dot_directories = options[:include_dot_directories]
|
39
|
+
path = Pathname.new(path) unless path.is_a?(Pathname)
|
40
|
+
path.find do |file|
|
41
|
+
puts "loading #{file}"
|
42
|
+
if !include_dot_directories && file.basename.to_s[0] == '.'
|
43
|
+
Find.prune
|
44
|
+
else
|
45
|
+
resource = load(file)
|
46
|
+
files << resource if resource
|
47
|
+
end
|
44
48
|
end
|
49
|
+
files
|
45
50
|
end
|
46
|
-
files
|
47
|
-
end
|
48
51
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
52
|
+
# Find all configured resource file classes
|
53
|
+
# @return [Array<Class>] Available resource file classes
|
54
|
+
def types
|
55
|
+
@types ||= constants.map { |c| const_get(c) }.select do |klass|
|
56
|
+
klass.is_a?(Class) && klass != Base && klass < Base
|
57
|
+
end
|
54
58
|
end
|
55
|
-
end
|
56
59
|
|
57
|
-
|
60
|
+
private
|
58
61
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
62
|
+
# find classes that can load the given path by file extension
|
63
|
+
def types_for_path(path)
|
64
|
+
path = path.is_a?(Pathname) ? path : Pathname.new(path)
|
65
|
+
types.select { |klass| klass.enabled? && extension_match(klass, path) }
|
66
|
+
end
|
64
67
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
def extension_match(klass, path)
|
69
|
+
filename = path.basename.to_s.downcase
|
70
|
+
klass.extensions.each do |extension|
|
71
|
+
# don't match end of line in case file has locale extension
|
72
|
+
return true if filename =~ /\.#{extension}/
|
73
|
+
end
|
74
|
+
false
|
70
75
|
end
|
71
|
-
false
|
72
76
|
end
|
73
77
|
end
|
74
78
|
end
|
75
79
|
|
76
80
|
require 'translatomatic/resource_file/base'
|
81
|
+
require 'translatomatic/resource_file/key_value_support'
|
77
82
|
require 'translatomatic/resource_file/yaml'
|
78
83
|
require 'translatomatic/resource_file/properties'
|
79
84
|
require 'translatomatic/resource_file/text'
|
@@ -85,3 +90,4 @@ require 'translatomatic/resource_file/plist'
|
|
85
90
|
require 'translatomatic/resource_file/resw'
|
86
91
|
require 'translatomatic/resource_file/subtitle'
|
87
92
|
require 'translatomatic/resource_file/csv'
|
93
|
+
require 'translatomatic/resource_file/po'
|
@@ -1,269 +1,203 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
# @return [boolean] True if the file format consists of keys and values
|
22
|
-
def self.is_key_value?
|
23
|
-
false
|
24
|
-
end
|
25
|
-
|
26
|
-
# @return [boolean] true if this resource file supports variable interpolation
|
27
|
-
def self.supports_variable_interpolation?
|
28
|
-
false
|
29
|
-
end
|
30
|
-
|
31
|
-
# Create a new resource file or load an existing file.
|
32
|
-
# If options[:locale] is unspecified, attempts to determine
|
33
|
-
# the locale of the file automatically, and if that fails,
|
34
|
-
# uses the default locale.
|
35
|
-
# Raises an exception if errors were encountered loading the file.
|
36
|
-
# @param path [String] Path to the file
|
37
|
-
# @param options [Hash<Symbol,String>] Options
|
38
|
-
# @return [Translatomatic::ResourceFile::Base] the resource file.
|
39
|
-
def initialize(path = nil, options = {})
|
40
|
-
raise "expected options hash" if options && !options.kind_of?(Hash)
|
41
|
-
raise t("file.unsupported", file: path) unless self.class.enabled?
|
42
|
-
@options = options || {}
|
43
|
-
@properties = {}
|
44
|
-
@path = path.nil? || path.kind_of?(Pathname) ? path : Pathname.new(path)
|
45
|
-
update_locale
|
46
|
-
init
|
47
|
-
load if @path && @path.exist?
|
48
|
-
end
|
49
|
-
|
50
|
-
# Save the resource file.
|
51
|
-
# @param target [Pathname] The destination path
|
52
|
-
# @param options [Hash<Symbol, Object>] Output format options
|
53
|
-
# @return [void]
|
54
|
-
def save(target = path, options = {})
|
55
|
-
raise "save(path) must be implemented by subclass"
|
56
|
-
end
|
57
|
-
|
58
|
-
# @return [String] The format of this resource file, e.g. "Properties"
|
59
|
-
def format
|
60
|
-
self.class.name.demodulize.downcase.to_sym
|
61
|
-
end
|
62
|
-
|
63
|
-
# Create a path for the current resource file with a given locale
|
64
|
-
# @param locale [String] The target locale
|
65
|
-
# @return [Pathname] The path of this resource file modified for the given locale
|
66
|
-
def locale_path(locale)
|
67
|
-
basename = path.sub_ext('').basename.to_s
|
68
|
-
|
69
|
-
extlist = extension_list
|
70
|
-
if extlist.length >= 2 && loc_idx = find_locale(extlist)
|
71
|
-
# extension(s) contains locale, replace it
|
72
|
-
extlist[loc_idx] = locale.to_s
|
73
|
-
elsif valid_locale?(basename)
|
74
|
-
# basename is a locale name, replace it
|
75
|
-
path.dirname + (locale.to_s + path.extname)
|
76
|
-
else
|
77
|
-
# remove any underscore and trailing text from basename
|
78
|
-
deunderscored = basename.sub(/_.*?$/, '')
|
79
|
-
# add _locale.ext
|
80
|
-
filename = deunderscored + "_" + locale.to_s + path.extname
|
81
|
-
path.dirname + filename
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# Set all properties
|
86
|
-
# @param properties [Hash<String,String>] New properties
|
87
|
-
def properties=(properties)
|
88
|
-
# use set rather that set @properties directly as subclasses override set()
|
89
|
-
properties.each do |key, value|
|
90
|
-
set(key, value)
|
91
|
-
end
|
92
|
-
end
|
1
|
+
module Translatomatic
|
2
|
+
module ResourceFile
|
3
|
+
# Base class for resource file implementations
|
4
|
+
# @abstract Subclasses implement different types of resource files
|
5
|
+
class Base
|
6
|
+
# @return [Hash<Symbol,Object] Options used in the constructor
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
# @return [Locale] The locale of the contents of this resource file
|
10
|
+
attr_accessor :locale
|
11
|
+
|
12
|
+
# @return [Pathname] The path to this resource file
|
13
|
+
attr_accessor :path
|
14
|
+
|
15
|
+
# @return [boolean] True if this resource format is enabled
|
16
|
+
def self.enabled?
|
17
|
+
true
|
18
|
+
end
|
93
19
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
20
|
+
# @return [String] Preferred character to use to separate the locale
|
21
|
+
# from the file basename
|
22
|
+
def self.preferred_locale_separator
|
23
|
+
'.'
|
24
|
+
end
|
100
25
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
def set(key, value)
|
106
|
-
@properties[key] = value
|
107
|
-
end
|
26
|
+
# @return [Array<String>] File extensions supported by this resource file
|
27
|
+
def self.extensions
|
28
|
+
raise 'extensions must be implemented by subclass'
|
29
|
+
end
|
108
30
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
31
|
+
# @return [boolean] True if the file format consists of keys and values
|
32
|
+
def self.key_value?
|
33
|
+
false
|
34
|
+
end
|
113
35
|
|
114
|
-
|
115
|
-
|
116
|
-
|
36
|
+
# @return [boolean] true if this resource file supports
|
37
|
+
# variable interpolation
|
38
|
+
def self.supports_variable_interpolation?
|
39
|
+
false
|
40
|
+
end
|
117
41
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
42
|
+
# Create a new resource file or load an existing file.
|
43
|
+
# If options[:locale] is unspecified, attempts to determine
|
44
|
+
# the locale of the file automatically, and if that fails,
|
45
|
+
# uses the default locale.
|
46
|
+
# Raises an exception if errors were encountered loading the file.
|
47
|
+
# @param path [String] Path to the file
|
48
|
+
# @param options [Hash<Symbol,String>] Options
|
49
|
+
# @return [Translatomatic::ResourceFile::Base] the resource file.
|
50
|
+
def initialize(path = nil, options = {})
|
51
|
+
raise 'expected options hash' if options && !options.is_a?(Hash)
|
52
|
+
raise t('file.unsupported', file: path) unless self.class.enabled?
|
53
|
+
initialize_attributes(path, options)
|
54
|
+
update_locale
|
55
|
+
init
|
56
|
+
try_load
|
57
|
+
end
|
127
58
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
59
|
+
# Save the resource file.
|
60
|
+
# @param target [Pathname] The destination path
|
61
|
+
# @param options [Hash<Symbol, Object>] Output format options
|
62
|
+
# @return [void]
|
63
|
+
def save(target = path, options = {})
|
64
|
+
raise 'save(path) must be implemented by subclass'
|
65
|
+
end
|
135
66
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
end
|
67
|
+
# @return [Symbol] The type of this resource file, e.g. ":properties"
|
68
|
+
def type
|
69
|
+
self.class.name.demodulize.downcase.to_sym
|
70
|
+
end
|
141
71
|
|
142
|
-
|
72
|
+
# Create a path for the current resource file with a given locale
|
73
|
+
# @param target_locale [String] The target locale
|
74
|
+
# @return [Pathname] The path of this resource file modified
|
75
|
+
# for the given locale
|
76
|
+
def locale_path(target_locale)
|
77
|
+
modify_path_locale(path, target_locale,
|
78
|
+
self.class.preferred_locale_separator)
|
79
|
+
end
|
143
80
|
|
144
|
-
|
81
|
+
# Set all properties
|
82
|
+
# @param properties [Hash<String,String>] New properties
|
83
|
+
def properties=(properties)
|
84
|
+
# use set rather that set @properties directly as subclasses
|
85
|
+
# override set()
|
86
|
+
properties.each do |key, value|
|
87
|
+
set(key, value)
|
88
|
+
end
|
89
|
+
end
|
145
90
|
|
146
|
-
|
147
|
-
|
148
|
-
|
91
|
+
# @return [Hash<String,String>] key -> value properties
|
92
|
+
def properties
|
93
|
+
@properties.dup
|
94
|
+
end
|
149
95
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
96
|
+
# Get the value of a property
|
97
|
+
# @param key [String] The name of the property
|
98
|
+
# @return [String] The value of the property
|
99
|
+
def get(key)
|
100
|
+
@properties[key]
|
101
|
+
end
|
154
102
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
103
|
+
# Get context of a property
|
104
|
+
# @param key [String] The name of the property
|
105
|
+
# @return [Array<String>] The property context, may be nil
|
106
|
+
def get_context(key)
|
107
|
+
@metadata.get_context(key)
|
108
|
+
end
|
160
109
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
# english locale if translation is missing.
|
169
|
-
t("file.created_by", options.merge({
|
170
|
-
locale: locale.language,
|
171
|
-
default: t("file.created_by", options.merge(locale: "en"))
|
172
|
-
})
|
173
|
-
)
|
174
|
-
end
|
110
|
+
# Set a property
|
111
|
+
# @param key [String] The name of the property
|
112
|
+
# @param value [String] The new value of the property
|
113
|
+
# @return [String] The new value of the property
|
114
|
+
def set(key, value)
|
115
|
+
@properties[key] = value
|
116
|
+
end
|
175
117
|
|
176
|
-
|
177
|
-
|
178
|
-
|
118
|
+
# @return [String] String representation of this file
|
119
|
+
def to_s
|
120
|
+
relative_path(path).to_s
|
121
|
+
end
|
179
122
|
|
180
|
-
|
181
|
-
|
182
|
-
|
123
|
+
# @return [Array<Text>] All property values split into sentences
|
124
|
+
def sentences
|
125
|
+
sentences = []
|
126
|
+
properties.each_value do |value|
|
127
|
+
string = build_text(value, locale)
|
128
|
+
sentences += string.sentences
|
129
|
+
end
|
130
|
+
sentences
|
131
|
+
end
|
183
132
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
133
|
+
# Create an interpolated variable string.
|
134
|
+
# @param name [String] The variable name
|
135
|
+
# @return [String] A string representing the interpolated variable, or
|
136
|
+
# nil if this resource file doesn't support variable interpolation.
|
137
|
+
def create_variable(name)
|
138
|
+
return nil unless self.class.supports_variable_interpolation?
|
139
|
+
raise 'create_variable(name) must be implemented by subclass'
|
140
|
+
end
|
191
141
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
tag = $1
|
198
|
-
elsif extlist.length >= 2 && loc_idx = find_locale(extlist)
|
199
|
-
# multiple parts to extension, e.g. index.html.en
|
200
|
-
tag = extlist[loc_idx]
|
201
|
-
elsif valid_locale?(basename)
|
202
|
-
# try to match on entire basename
|
203
|
-
# (support for rails en.yml)
|
204
|
-
tag = basename
|
205
|
-
elsif valid_locale?(path.parent.basename)
|
206
|
-
# try to match on parent directory, e.g. strings/en-US/text.resw
|
207
|
-
tag = path.parent.basename
|
208
|
-
end
|
142
|
+
# @return [Regexp] A regexp used to match interpolated variables
|
143
|
+
def variable_regex
|
144
|
+
return nil unless self.class.supports_variable_interpolation?
|
145
|
+
raise 'variable_regex must be implemented by subclass'
|
146
|
+
end
|
209
147
|
|
210
|
-
|
211
|
-
end
|
148
|
+
private
|
212
149
|
|
213
|
-
|
214
|
-
|
215
|
-
|
150
|
+
include Translatomatic::Util
|
151
|
+
include Translatomatic::Flattenation
|
152
|
+
include Translatomatic::PathUtils
|
153
|
+
include Translatomatic::DefineOptions
|
216
154
|
|
217
|
-
|
218
|
-
|
219
|
-
def find_locale(list)
|
220
|
-
list.find_index { |i| valid_locale?(i) }
|
221
|
-
end
|
155
|
+
# called by constructor before load
|
156
|
+
def init; end
|
222
157
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
path.parent + filename
|
228
|
-
end
|
158
|
+
# load contents from @path
|
159
|
+
def load
|
160
|
+
raise 'load must be implemented by subclass'
|
161
|
+
end
|
229
162
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
end
|
163
|
+
def try_load
|
164
|
+
return unless @path
|
165
|
+
raise t('file.not_found', file: path) unless @path.exist?
|
166
|
+
load
|
167
|
+
end
|
236
168
|
|
237
|
-
|
238
|
-
|
239
|
-
|
169
|
+
def initialize_attributes(path, options)
|
170
|
+
@options = options || {}
|
171
|
+
@properties = {}
|
172
|
+
@metadata = Metadata.new
|
173
|
+
@path = path.nil? || path.is_a?(Pathname) ? path : Pathname.new(path)
|
174
|
+
end
|
240
175
|
|
241
|
-
|
242
|
-
|
243
|
-
|
176
|
+
def update_locale
|
177
|
+
default = Translatomatic::Locale.default
|
178
|
+
locale = @options[:locale] || detect_path_locale(path) || default
|
179
|
+
@locale = Translatomatic::Locale.parse(locale)
|
180
|
+
raise t('file.unknown_locale') unless @locale && @locale.language
|
244
181
|
end
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
182
|
+
|
183
|
+
def created_by
|
184
|
+
options = {
|
185
|
+
app: 'Translatomatic',
|
186
|
+
version: Translatomatic::VERSION,
|
187
|
+
date: I18n.l(Time.now),
|
188
|
+
locale: locale.language,
|
189
|
+
url: Translatomatic::URL
|
190
|
+
}
|
191
|
+
t('file.created_by', options)
|
249
192
|
end
|
250
|
-
end
|
251
193
|
|
252
|
-
|
253
|
-
|
194
|
+
def created_by?
|
195
|
+
@created_by
|
196
|
+
end
|
254
197
|
|
255
|
-
|
256
|
-
|
257
|
-
children = flatten(value)
|
258
|
-
children.each do |ck, cv|
|
259
|
-
result[key + "." + ck] = cv
|
198
|
+
def parsing_error(error)
|
199
|
+
raise StandardError, error
|
260
200
|
end
|
261
|
-
else
|
262
|
-
result[key] = value
|
263
201
|
end
|
264
202
|
end
|
265
|
-
|
266
|
-
def needs_flatten?(value)
|
267
|
-
value.kind_of?(Array) || value.kind_of?(Hash)
|
268
|
-
end
|
269
203
|
end
|