i18n_po_tools 0.9.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 +15 -0
- data/.gitignore +34 -0
- data/Design.md +0 -0
- data/Design.ru.md +188 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/Rakefile +1 -0
- data/Readme.md +77 -0
- data/Readme.ru.md +191 -0
- data/bin/i18n-po +9 -0
- data/i18n_yml_tools.gemspec +47 -0
- data/lib/i18n_po_tools/cli.rb +171 -0
- data/lib/i18n_po_tools/core_ext/dotted_hash.rb +34 -0
- data/lib/i18n_po_tools/data/languages.yml +26 -0
- data/lib/i18n_po_tools/data/plurals.rb +116 -0
- data/lib/i18n_po_tools/formats/android_format.rb +28 -0
- data/lib/i18n_po_tools/formats/base_format.rb +57 -0
- data/lib/i18n_po_tools/formats/basic_flat_yaml_format.rb +18 -0
- data/lib/i18n_po_tools/formats/csv_format.rb +31 -0
- data/lib/i18n_po_tools/formats/ios_format.rb +17 -0
- data/lib/i18n_po_tools/formats/po_format.rb +115 -0
- data/lib/i18n_po_tools/formats/rails_yaml_format.rb +32 -0
- data/lib/i18n_po_tools/formats.rb +32 -0
- data/lib/i18n_po_tools/plural_message.rb +18 -0
- data/lib/i18n_po_tools/utils/data.rb +25 -0
- data/lib/i18n_po_tools/utils/ios_strings.rb +83 -0
- data/lib/i18n_po_tools/utils/locfile.rb +164 -0
- data/lib/i18n_po_tools/utils/po.rb +87 -0
- data/lib/i18n_po_tools/version.rb +3 -0
- data/lib/i18n_po_tools.rb +24 -0
- metadata +185 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module I18nPoTools
|
4
|
+
class CLI
|
5
|
+
def parse_options(options)
|
6
|
+
opt_parser = OptionParser.new do |opt|
|
7
|
+
opt.banner = "Usage: i18n-po [OPTIONS]"
|
8
|
+
opt.separator ""
|
9
|
+
opt.separator "Utils to convert translations from source formats to PO/POT Gettex and vise"
|
10
|
+
opt.separator "versa. It allows to separate translations from development of apps."
|
11
|
+
opt.separator ""
|
12
|
+
opt.separator "Supported input/output formats:"
|
13
|
+
opt.separator "* iOS and OS X String Resources"
|
14
|
+
opt.separator "* Android String XML"
|
15
|
+
opt.separator "* Gettext PO/POT"
|
16
|
+
opt.separator "* Rails YAML"
|
17
|
+
opt.separator "* Basic flat YAML"
|
18
|
+
opt.separator "* CVS for easy exchange with other apps"
|
19
|
+
opt.separator ""
|
20
|
+
opt.separator "Direct converation between any formats supported."
|
21
|
+
opt.separator ""
|
22
|
+
opt.separator "Rails YAML and PO supports plural forms of messages."
|
23
|
+
opt.separator ""
|
24
|
+
opt.separator ""
|
25
|
+
opt.separator "Options"
|
26
|
+
|
27
|
+
|
28
|
+
opt.on("-i","--input FILENAME","input filename (default STDIN)") do |input_filename|
|
29
|
+
options[:input_filename] = input_filename
|
30
|
+
end
|
31
|
+
opt.on("-b","--base FILENAME","base language input filename (only for po output mode)") do |base_filename|
|
32
|
+
options[:base_filename] = base_filename
|
33
|
+
end
|
34
|
+
opt.on("-o","--output FILENAME","output filename (default STDOUT)") do |output_filename|
|
35
|
+
options[:output_filename] = output_filename
|
36
|
+
end
|
37
|
+
|
38
|
+
opt.on("-f","--from FORMAT", FORMATS, "input file format (default is autodetect by ext)", "(#{FORMATS.join(', ')})") do |input_format|
|
39
|
+
options[:input_format] = input_format
|
40
|
+
end
|
41
|
+
|
42
|
+
opt.on("-t","--to FORMAT", FORMATS, "output file format (default is autodetect by ext)", "(#{FORMATS.join(', ')})") do |output_format|
|
43
|
+
options[:output_format] = output_format
|
44
|
+
end
|
45
|
+
|
46
|
+
opt.on("-l","--language LANGUAGE","input file language (only for po or rails_yaml output mode)") do |language|
|
47
|
+
options[:language] = language
|
48
|
+
end
|
49
|
+
|
50
|
+
opt.on("-h","--help","help") do
|
51
|
+
puts opt_parser
|
52
|
+
exit(0)
|
53
|
+
end
|
54
|
+
|
55
|
+
opt.separator ""
|
56
|
+
opt.separator "Examples:"
|
57
|
+
opt.separator "1) First import (generation of PO file) from Rails YAML file:"
|
58
|
+
opt.separator "i18n-po --input ~/projects/rails_app/config/locales/devise.en.yml --language en \\"
|
59
|
+
opt.separator " --base ~/projects/rails_app/config/locales/devise.ru.yml \\"
|
60
|
+
opt.separator " --output ~/projects/translations/rails_app/devise.en.po"
|
61
|
+
opt.separator ""
|
62
|
+
opt.separator "2) Generation of translation template (POT file) from Rails YAML file:"
|
63
|
+
opt.separator "i18n-po --input ~/projects/rails_app/config/locales/devise.ru.yml \\"
|
64
|
+
opt.separator " --output ~/projects/translations/rails_app/devise.en.pot"
|
65
|
+
opt.separator ""
|
66
|
+
opt.separator "3) Generation of Rails YAML file from PO file:"
|
67
|
+
opt.separator "i18n-po --input ~/projects/translations/rails_app/devise.en.po --language en \\"
|
68
|
+
opt.separator " --output ~/projects/rails_app/config/locales/devise.en.yml"
|
69
|
+
opt.separator ""
|
70
|
+
opt.separator "4) Translation convertation from iOS to Android format:"
|
71
|
+
opt.separator "i18n-po --input ~/projects/ios_app/Localizable.strings \\"
|
72
|
+
opt.separator " --output ~/projects/android_app/en/strings.xml"
|
73
|
+
opt.separator ""
|
74
|
+
opt.separator "Homepage: http://github.com/stepin/i18n-po-tools"
|
75
|
+
opt.separator ""
|
76
|
+
end
|
77
|
+
|
78
|
+
opt_parser.parse!
|
79
|
+
|
80
|
+
#default help
|
81
|
+
if options.count == 0
|
82
|
+
puts opt_parser
|
83
|
+
exit(1)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def check_options(options)
|
88
|
+
if options[:input_format].blank?
|
89
|
+
if options[:input_filename].present?
|
90
|
+
ext = File.extname(options[:input_filename])[1..-1].to_sym
|
91
|
+
options[:input_format] = EXT_TO_FORMAT[ext]
|
92
|
+
if options[:input_format].blank?
|
93
|
+
STDERR.puts "ERROR: unknown input format, please define it manually."
|
94
|
+
exit(1)
|
95
|
+
end
|
96
|
+
else
|
97
|
+
STDERR.puts "ERROR: input format should be defined for STDIN stream."
|
98
|
+
exit(1)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
if options[:output_format].blank?
|
103
|
+
if options[:output_filename].present?
|
104
|
+
ext = File.extname(options[:output_filename])[1..-1].to_sym
|
105
|
+
options[:output_format] = EXT_TO_FORMAT[ext]
|
106
|
+
if options[:output_format].blank?
|
107
|
+
STDERR.puts "ERROR: unknown output format, please define it manually."
|
108
|
+
exit(1)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
STDERR.puts "ERROR: output format should be defined for STDOUT stream."
|
112
|
+
exit(1)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
options[:input_format] = options[:input_format].to_sym
|
117
|
+
options[:output_format] = options[:output_format].to_sym
|
118
|
+
|
119
|
+
if options[:output_format] == :po
|
120
|
+
if options[:base_filename].blank?
|
121
|
+
STDERR.puts "ERROR: base filename should be defined for po output mode."
|
122
|
+
exit(1)
|
123
|
+
end
|
124
|
+
if options[:language].blank?
|
125
|
+
STDERR.puts "ERROR: language should be defined for po output mode."
|
126
|
+
exit(1)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
if options[:base_filename].present?
|
130
|
+
STDERR.puts "WARNING: base filename is not used in this mode."
|
131
|
+
end
|
132
|
+
if options[:output_format] == :rails_yaml
|
133
|
+
if options[:language].blank?
|
134
|
+
STDERR.puts "ERROR: language should be defined for rails_yaml output mode."
|
135
|
+
exit(1)
|
136
|
+
end
|
137
|
+
else
|
138
|
+
if options[:language].present?
|
139
|
+
STDERR.puts "WARNING: language is not used in this mode."
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
if options[:input_format] == options[:output_format]
|
146
|
+
STDERR.puts "ERROR: same format, just use copy command."
|
147
|
+
exit(1)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.start
|
152
|
+
new.start
|
153
|
+
end
|
154
|
+
|
155
|
+
def start
|
156
|
+
options = {}
|
157
|
+
parse_options(options)
|
158
|
+
check_options(options)
|
159
|
+
|
160
|
+
reader = I18nPoTools::Formats::Factory.get(options[:input_format])
|
161
|
+
writer = I18nPoTools::Formats::Factory.get(options[:output_format])
|
162
|
+
|
163
|
+
input_data = reader.read_with_options(options[:input_filename], options, :input)
|
164
|
+
base_data = nil
|
165
|
+
if options[:output_format] == :po
|
166
|
+
base_data = reader.read_with_options(options[:base_filename], options, :base)
|
167
|
+
end
|
168
|
+
writer.write_with_options(input_data, base_data, options)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
def implode(divider = '.')
|
4
|
+
Hash.implode(self, divider)
|
5
|
+
end
|
6
|
+
|
7
|
+
def explode(divider = '.')
|
8
|
+
Hash.explode(self, divider)
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
def self.implode(hash, divider = '.', recursive_key = "")
|
13
|
+
hash.each_with_object({}) do |(k, v), ret|
|
14
|
+
key = recursive_key + k.to_s
|
15
|
+
if v.is_a? Hash
|
16
|
+
ret.merge! implode(v, divider, key + divider)
|
17
|
+
else
|
18
|
+
ret[key] = v
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.explode(hash, divider = '.')
|
24
|
+
hash.each_with_object({}) do |(k, v), ret|
|
25
|
+
path = k.split(divider)
|
26
|
+
h = ret
|
27
|
+
path[0...-1].each do |path_part|
|
28
|
+
h[path_part] = {} if h[path_part].nil?
|
29
|
+
h = h[path_part]
|
30
|
+
end
|
31
|
+
h[path.last] = v
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#
|
2
|
+
# Languages names with PO pliral rules.
|
3
|
+
# Plural rules and keys for Rails in the plurals.rb file.
|
4
|
+
#
|
5
|
+
# More data from:
|
6
|
+
# http://git.openstreetmap.org/rails.git/blob/e2c81091a02ad7e69165a9e30a89aecd2c8db531:/config/languages.yml
|
7
|
+
#
|
8
|
+
# PO plural rules:
|
9
|
+
# http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
|
10
|
+
#
|
11
|
+
ru:
|
12
|
+
english: Russian
|
13
|
+
native: Русский
|
14
|
+
po_plural_rule: "nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;"
|
15
|
+
en:
|
16
|
+
english: English
|
17
|
+
native: English
|
18
|
+
po_plural_rule: "nplurals=2; plural=(n != 1);"
|
19
|
+
pl:
|
20
|
+
english: Polish
|
21
|
+
native: Polski
|
22
|
+
po_plural_rule: "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"
|
23
|
+
fr:
|
24
|
+
english: French
|
25
|
+
native: Français
|
26
|
+
po_plural_rule: "nplurals=2; plural=(n > 1);"
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# From:
|
4
|
+
# https://raw.githubusercontent.com/svenfuchs/i18n/master/test/test_data/locales/plurals.rb
|
5
|
+
#
|
6
|
+
|
7
|
+
{
|
8
|
+
:af => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
9
|
+
:am => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
10
|
+
:ar => { :i18n => { :plural => { :keys => [:zero, :one, :two, :few, :many, :other], :rule => lambda { |n| n == 0 ? :zero : n == 1 ? :one : n == 2 ? :two : [3, 4, 5, 6, 7, 8, 9, 10].include?(n % 100) ? :few : [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99].include?(n % 100) ? :many : :other } } } },
|
11
|
+
:az => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
12
|
+
:be => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } },
|
13
|
+
:bg => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
14
|
+
:bh => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
15
|
+
:bn => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
16
|
+
:bo => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
17
|
+
:bs => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } },
|
18
|
+
:ca => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
19
|
+
:cs => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n == 1 ? :one : [2, 3, 4].include?(n) ? :few : :other } } } },
|
20
|
+
:cy => { :i18n => { :plural => { :keys => [:one, :two, :many, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : n == 8 || n == 11 ? :many : :other } } } },
|
21
|
+
:da => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
22
|
+
:de => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
23
|
+
:dz => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
24
|
+
:el => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
25
|
+
:en => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
26
|
+
:eo => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
27
|
+
:es => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
28
|
+
:et => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
29
|
+
:eu => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
30
|
+
:fa => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
31
|
+
:fi => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
32
|
+
:fil => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
33
|
+
:fo => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
34
|
+
:fr => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n.between?(0, 2) && n != 2 ? :one : :other } } } },
|
35
|
+
:fur => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
36
|
+
:fy => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
37
|
+
:ga => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } },
|
38
|
+
:gl => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
39
|
+
:gu => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
40
|
+
:guw => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
41
|
+
:ha => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
42
|
+
:he => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
43
|
+
:hi => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
44
|
+
:hr => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } },
|
45
|
+
:hu => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
46
|
+
:id => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
47
|
+
:is => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
48
|
+
:it => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
49
|
+
:iw => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
50
|
+
:ja => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
51
|
+
:jv => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
52
|
+
:ka => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
53
|
+
:km => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
54
|
+
:kn => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
55
|
+
:ko => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
56
|
+
:ku => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
57
|
+
:lb => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
58
|
+
:ln => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
59
|
+
:lt => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n % 10 == 1 && ![11, 12, 13, 14, 15, 16, 17, 18, 19].include?(n % 100) ? :one : [2, 3, 4, 5, 6, 7, 8, 9].include?(n % 10) && ![11, 12, 13, 14, 15, 16, 17, 18, 19].include?(n % 100) ? :few : :other } } } },
|
60
|
+
:lv => { :i18n => { :plural => { :keys => [:zero, :one, :other], :rule => lambda { |n| n == 0 ? :zero : n % 10 == 1 && n % 100 != 11 ? :one : :other } } } },
|
61
|
+
:mg => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
62
|
+
:mk => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n % 10 == 1 ? :one : :other } } } },
|
63
|
+
:ml => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
64
|
+
:mn => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
65
|
+
:mo => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n == 1 ? :one : n == 0 ? :few : :other } } } },
|
66
|
+
:mr => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
67
|
+
:ms => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
68
|
+
:mt => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n == 1 ? :one : n == 0 || [2, 3, 4, 5, 6, 7, 8, 9, 10].include?(n % 100) ? :few : [11, 12, 13, 14, 15, 16, 17, 18, 19].include?(n % 100) ? :many : :other } } } },
|
69
|
+
:my => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
70
|
+
:nah => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
71
|
+
:nb => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
72
|
+
:ne => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
73
|
+
:nl => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
74
|
+
:nn => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
75
|
+
:no => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
76
|
+
:nso => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
77
|
+
:om => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
78
|
+
:or => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
79
|
+
:pa => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
80
|
+
:pap => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
81
|
+
:pl => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n == 1 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : :other } } } },
|
82
|
+
:ps => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
83
|
+
:pt => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
84
|
+
:"pt-PT" => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
85
|
+
:ro => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n == 1 ? :one : n == 0 ? :few : :other } } } },
|
86
|
+
:ru => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } },
|
87
|
+
:se => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } },
|
88
|
+
:sh => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } },
|
89
|
+
:sk => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n == 1 ? :one : [2, 3, 4].include?(n) ? :few : :other } } } },
|
90
|
+
:sl => { :i18n => { :plural => { :keys => [:one, :two, :few, :other], :rule => lambda { |n| n % 100 == 1 ? :one : n % 100 == 2 ? :two : [3, 4].include?(n % 100) ? :few : :other } } } },
|
91
|
+
:sma => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } },
|
92
|
+
:smi => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } },
|
93
|
+
:smj => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } },
|
94
|
+
:smn => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } },
|
95
|
+
:sms => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } },
|
96
|
+
:so => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
97
|
+
:sq => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
98
|
+
:sr => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } },
|
99
|
+
:sv => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
100
|
+
:sw => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
101
|
+
:ta => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
102
|
+
:te => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
103
|
+
:th => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
104
|
+
:ti => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
105
|
+
:tk => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
106
|
+
:tl => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
107
|
+
:to => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
108
|
+
:tr => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
109
|
+
:uk => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } },
|
110
|
+
:ur => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
|
111
|
+
:vi => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
112
|
+
:wa => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
|
113
|
+
:yo => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
114
|
+
:zh => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } },
|
115
|
+
:zu => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }
|
116
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module I18nPoTools
|
4
|
+
module Formats
|
5
|
+
class AndroidFormat < BaseFormat
|
6
|
+
def read(input_filename)
|
7
|
+
content = read_file(input_filename)
|
8
|
+
doc = Nokogiri.XML(content, nil, 'UTF-8')
|
9
|
+
h = {}
|
10
|
+
doc.xpath('//resources/string').each do |message_token|
|
11
|
+
h[message_token["name"]] = message_token.inner_html
|
12
|
+
end
|
13
|
+
h
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(output_filename, data)
|
17
|
+
content = "<!-- "+banner_msg+" -->\n"
|
18
|
+
content += "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
19
|
+
content += "<resources>\n\n"
|
20
|
+
data.each_pair do |k,v|
|
21
|
+
content += " <string name=\""+(k.nil? ? '':k)+"\">"+(v.nil? ? '':Nokogiri::HTML.fragment(v).to_html)+"</string>\n"
|
22
|
+
end if data.present?
|
23
|
+
content += "</resources>\n"
|
24
|
+
write_file(output_filename, content)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module I18nPoTools
|
2
|
+
module Formats
|
3
|
+
class BaseFormat
|
4
|
+
attr_accessor :options
|
5
|
+
|
6
|
+
def read_file(input_filename)
|
7
|
+
if input_filename.present?
|
8
|
+
if file_encoding.index('UTF-16')
|
9
|
+
mode = "rb:#{file_encoding}"
|
10
|
+
else
|
11
|
+
mode = "r:#{file_encoding}"
|
12
|
+
end
|
13
|
+
|
14
|
+
File.open(input_filename, mode, &:read)
|
15
|
+
else
|
16
|
+
$stdin.read
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def write_file(output_filename, data)
|
21
|
+
if output_filename.present?
|
22
|
+
File.open(output_filename, "w:"+file_encoding) do |f|
|
23
|
+
f << data
|
24
|
+
end
|
25
|
+
else
|
26
|
+
puts data
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_with_options(data, base_data, options)
|
31
|
+
@options = options
|
32
|
+
write(options[:output_filename], data)
|
33
|
+
end
|
34
|
+
|
35
|
+
def read_with_options(filename, options, mode)
|
36
|
+
@options = options
|
37
|
+
read(filename)
|
38
|
+
end
|
39
|
+
|
40
|
+
def read(filename)
|
41
|
+
raise "Not Implemented"
|
42
|
+
end
|
43
|
+
|
44
|
+
def write(filename, data)
|
45
|
+
raise "Not Implemented"
|
46
|
+
end
|
47
|
+
|
48
|
+
def banner_msg
|
49
|
+
"Generated by i18n-po #{I18nPoTools::VERSION} on #{Time.now.strftime("%Y-%m-%d %H:%M:%S")} by #{ENV['USER']} on #{`hostname`.gsub("\n",'')}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def file_encoding
|
53
|
+
'UTF-8'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module I18nPoTools
|
4
|
+
module Formats
|
5
|
+
class BasicFlatYamlFormat < BaseFormat
|
6
|
+
def read(input_filename)
|
7
|
+
content = read_file(input_filename)
|
8
|
+
flat_hash = YAML.load(content)
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(output_filename, data)
|
12
|
+
content = "# "+ banner_msg + "\n"
|
13
|
+
content += data.to_yaml
|
14
|
+
write_file(output_filename, content)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module I18nPoTools
|
4
|
+
module Formats
|
5
|
+
class CsvFormat < BaseFormat
|
6
|
+
def read(input_filename)
|
7
|
+
content = read_file(input_filename)
|
8
|
+
h = {}
|
9
|
+
first = true
|
10
|
+
CSV.parse(content) do |row|
|
11
|
+
if first
|
12
|
+
first = false
|
13
|
+
else
|
14
|
+
h[row[0]] = row[1]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
h
|
18
|
+
end
|
19
|
+
|
20
|
+
def write(output_filename, data)
|
21
|
+
content = CSV.generate do |csv|
|
22
|
+
csv << ["Key","Translation"]
|
23
|
+
data.each_pair do |k,v|
|
24
|
+
csv << [k,v]
|
25
|
+
end if data.present?
|
26
|
+
end
|
27
|
+
write_file(output_filename, content)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'i18n_po_tools/utils/ios_strings'
|
2
|
+
|
3
|
+
module I18nPoTools
|
4
|
+
module Formats
|
5
|
+
class IosFormat < BaseFormat
|
6
|
+
def read(input_filename)
|
7
|
+
content = read_file(input_filename)
|
8
|
+
IosStrings.strings_to_hash(content)
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(output_filename, data)
|
12
|
+
content = IosStrings.hash_to_strings(data,banner_msg)
|
13
|
+
write_file(output_filename, content)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'get_pomo'
|
2
|
+
require 'i18n_po_tools/utils/po'
|
3
|
+
require 'i18n_po_tools/utils/data'
|
4
|
+
require 'i18n_po_tools/plural_message'
|
5
|
+
|
6
|
+
module I18nPoTools
|
7
|
+
module Formats
|
8
|
+
class PoFormat < BaseFormat
|
9
|
+
def initialize(options = {})
|
10
|
+
@pot = options.fetch(:pot, false)
|
11
|
+
end
|
12
|
+
|
13
|
+
def read(input_filename)
|
14
|
+
content = read_file(input_filename)
|
15
|
+
|
16
|
+
translations = GetPomo::PoFile.parse(content)
|
17
|
+
h = {}
|
18
|
+
translations.each do |t|
|
19
|
+
t.fix_after_read
|
20
|
+
|
21
|
+
key = t.msgctxt
|
22
|
+
if key.blank?
|
23
|
+
if t.msgid.present?
|
24
|
+
STDERR.puts "WARNING: #{msgid} can not be imported (empty key in the msgctxt field)"
|
25
|
+
end
|
26
|
+
next
|
27
|
+
end
|
28
|
+
|
29
|
+
if t.plural?
|
30
|
+
if @pot
|
31
|
+
h[key] = PluralMessage.new(translations: t.msgid)
|
32
|
+
STDERR.puts "WARNING: Plural forms for #{key} can be trancated (only 2 forms for msgid, format limitation)"
|
33
|
+
else
|
34
|
+
h[key] = PluralMessage.new(translations: t.msgstr)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
if @pot
|
38
|
+
h[key] = t.msgid
|
39
|
+
else
|
40
|
+
h[key] = t.msgstr
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
h
|
45
|
+
end
|
46
|
+
|
47
|
+
def write_with_options(data, base_data, options)
|
48
|
+
@options = options
|
49
|
+
|
50
|
+
translations = []
|
51
|
+
translations << header_entity(@pot)
|
52
|
+
|
53
|
+
if @pot
|
54
|
+
data.each do |k,v|
|
55
|
+
translations << message_entity(k,v,nil)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
base_data.each do |k,v|
|
59
|
+
translations << message_entity(k,v,data[k])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
po = GetPomo::PoFile.new(:translations=>translations)
|
64
|
+
content = po.to_text
|
65
|
+
write_file(options[:output_filename], content)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def message_entity(key,base,translated)
|
70
|
+
base = base.presence || key
|
71
|
+
|
72
|
+
msg = GetPomo::Translation.new
|
73
|
+
msg.msgctxt = key
|
74
|
+
if base.is_a?(PluralMessage)
|
75
|
+
msg.msgid = base.to_a
|
76
|
+
if translated.present?
|
77
|
+
msg.msgstr = translated.is_a?(PluralMessage) ? translated.to_a : [translated]
|
78
|
+
else
|
79
|
+
msg.msgstr = nil
|
80
|
+
end
|
81
|
+
else
|
82
|
+
msg.msgid = base
|
83
|
+
msg.msgstr = translated
|
84
|
+
end
|
85
|
+
msg.fix_before_save
|
86
|
+
msg
|
87
|
+
end
|
88
|
+
|
89
|
+
def header_entity(pot)
|
90
|
+
headers = {}
|
91
|
+
headers["X-Generator"] = banner_msg
|
92
|
+
headers["Project-Id-Version"] = "PACKAGE VERSION"
|
93
|
+
headers["POT-Creation-Date"] = Time.now
|
94
|
+
headers["PO-Revision-Date"] = "YEAR-MO-DA HO:MI+ZONE"
|
95
|
+
headers["Last-Translator"] = "FULL NAME <EMAIL@ADDRESS>"
|
96
|
+
unless pot
|
97
|
+
headers["Language"] = options[:language]
|
98
|
+
language_data = Data.load_languages[options[:language]]
|
99
|
+
if language_data.present?
|
100
|
+
plural_rule = language_data["po_plural_rule"]
|
101
|
+
headers["Plural-Forms"] = plural_rule if plural_rule.present?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
headers["MIME-Version"] = "1.0"
|
105
|
+
headers["Content-Type"] = "text/plain; charset=UTF-8"
|
106
|
+
headers["Content-Transfer-Encoding"] = "8bit"
|
107
|
+
|
108
|
+
header = GetPomo::Translation.new
|
109
|
+
header.as_header(headers)
|
110
|
+
header.fix_before_save
|
111
|
+
header
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module I18nPoTools
|
4
|
+
module Formats
|
5
|
+
#NOTE: Yaml preformatted mode (: | ) do not work
|
6
|
+
#TODO: plurals support
|
7
|
+
class RailsYamlFormat < BaseFormat
|
8
|
+
def read(input_filename)
|
9
|
+
content = read_file(input_filename)
|
10
|
+
rails_yaml = YAML.load(content)
|
11
|
+
|
12
|
+
locales = rails_yaml.keys
|
13
|
+
if locales.length > 1
|
14
|
+
raise "Only one language per file supported"
|
15
|
+
end
|
16
|
+
|
17
|
+
flat_hash = rails_yaml[locales.first].implode
|
18
|
+
#TODO: plurals
|
19
|
+
flat_hash
|
20
|
+
end
|
21
|
+
|
22
|
+
def write(output_filename, data)
|
23
|
+
content = "# "+banner_msg+"\n"
|
24
|
+
|
25
|
+
data_with_locale = {}
|
26
|
+
data_with_locale[options[:language]] = data.explode
|
27
|
+
content += data_with_locale.to_yaml
|
28
|
+
write_file(output_filename, content)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|