File: po.rb

package info (click to toggle)
mkvtoolnix 92.0-1
  • links: PTS
  • area: main
  • in suites: trixie
  • size: 58,620 kB
  • sloc: cpp: 216,810; ruby: 11,403; xml: 8,058; ansic: 6,885; sh: 4,884; python: 1,041; perl: 191; makefile: 113; awk: 16; javascript: 4
file content (279 lines) | stat: -rw-r--r-- 8,193 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
def format_string_for_po str
  return '"' + str.gsub(/"/, '\"') + '"' unless /\\n./.match(str)

  ([ '""' ] + str.split(/(?<=\\n)/).map { |part| '"' + part.gsub(/"/, '\"') + '"' }).join("\n")
end

def unformat_string_for_po str
  str.gsub(/^"|"$/, '').gsub(/\\"/, '"')
end

def fix_po_msgstr_plurals items
  return items if (items.size == 0) || !items.first.key?(:comments)

  matches = %r{nplurals=(\d+)}.match(items.first[:comments].join(""))

  return items if !matches

  num_plurals = matches[1].to_i

  return items if num_plurals <= 0

  items.each do |item|
    next unless item.key?(:msgstr)
    item[:msgstr] = item[:msgstr][0..num_plurals - 1] if item[:msgstr].size > num_plurals
  end

  return items
end

def read_po file_name
  items   = [ { comments: [] } ]
  msgtype = nil
  line_no = 0
  started = false

  add_line = lambda do |type, to_add|
    items.last[type] ||= []
    items.last[type]  += to_add if to_add.is_a?(Array)
    items.last[type]  << to_add if to_add.is_a?(String)
  end

  IO.readlines(file_name).each do |line|
    line_no += 1
    line     = line.force_encoding("UTF-8").chomp

    if !line.empty? && !started
      items.last[:line] = line_no
      started           = true
    end

    if line.empty?
      items << {} unless items.last.keys.empty?
      msgtype = nil
      started = false

    elsif items.size == 1
      add_line.call :comments, line

    elsif /^#:\s*(.+)/.match(line)
      add_line.call :sources, $1.split(/\s+/)

    elsif /^#,\s*(.+)/.match(line)
      add_line.call :flags, $1.split(/,\s*/)

    elsif /^#\./.match(line)
      add_line.call :instructions, line

    elsif /^#~/.match(line)
      add_line.call :obsolete, line

    elsif /^#\|/.match(line)
      add_line.call :suggestions, line

    elsif /^#\s/.match(line)
      add_line.call :comments, line

    elsif /^ ( msgid(?:_plural)? | msgstr (?: \[ \d+ \])? ) \s* (.+)/x.match(line)
      type, string = $1, $2
      msgtype      = type.gsub(/\[.*/, '').to_sym

      items.last[msgtype] ||= []
      items.last[msgtype]  << unformat_string_for_po(string)

    elsif /^"/.match(line)
      fail "read_po: #{file_name}:#{line_no}: string entry without prior msgid/msgstr for »#{line}«" unless msgtype
      items.last[msgtype].last << unformat_string_for_po(line)

    else
      fail "read_po: #{file_name}:#{line_no}: unrecognized line type for »#{line}«"

    end
  end

  items.pop if items.last.keys.empty?

  return fix_po_msgstr_plurals items
end

def write_po file_name, items
  File.open(file_name, "w") do |file|

    items.each do |item|
      if item[:obsolete]
        file.puts(item[:obsolete].join("\n"))
        file.puts
        next
      end

      if item[:comments] && !item[:comments].empty?
        file.puts(item[:comments].join("\n"))
      end

      if item[:instructions] && !item[:instructions].empty?
        file.puts(item[:instructions].join("\n"))
      end

      if item[:sources] && !item[:sources].empty?
        file.puts(item[:sources].map { |source| "#: #{source}" }.join("\n").gsub(/,$/, ''))
      end

      if item[:flags] && !item[:flags].empty?
        file.puts("#, " + item[:flags].join(", "))
      end

      if item[:suggestions] && !item[:suggestions].empty?
        file.puts(item[:suggestions].join("\n"))
      end

      if item[:msgid]
        file.puts("msgid " + format_string_for_po(item[:msgid].first))
      end

      if item[:msgid_plural]
        file.puts("msgid_plural " + format_string_for_po(item[:msgid_plural].first))
      end

      if item[:msgstr]
        idx = 0

        item[:msgstr].each do |msgstr|
          suffix  = item[:msgid_plural] ? "[#{idx}]" : ""
          idx    += 1
          file.puts("msgstr#{suffix} " + format_string_for_po(msgstr))
        end
      end

      file.puts
    end
  end
end

def normalize_po file
  runq_code "NORMALIZE-PO", :target => file do
    write_po file, read_po(file)
  end
end

def replace_po_meta_info orig_metas, transifex_meta, key
  new_value = /"#{key}: \s+ (.+?) \\n"/x.match(transifex_meta)[1]
  # puts "looking for #{key} in #{transifex_meta}"
  # puts "  new val #{new_value}"
  return unless new_value

  orig_metas.each { |meta| meta.gsub!(/"#{key}: \s+ .+? \\n"/x, "\"#{key}: #{new_value}\\n\"") }
end

def merge_po orig_items, updated_items, options = {}
  translated = Hash[ *updated_items.
    select { |item| item[:msgid] && item[:msgid].first && !item[:msgid].first.empty? && item[:msgstr] && !item[:msgstr].empty? && !item[:msgstr].first.empty? }.
    map    { |item| [ item[:msgid].first, item ] }.
    flatten(1)
  ]

  update_meta_info = false

  orig_items.each do |orig_item|
    next if !orig_item[:msgid] || orig_item[:msgid].empty? || orig_item[:msgid].first.empty?

    updated_item = translated[ orig_item[:msgid].first ]

    next if !updated_item

    next if (orig_item[:msgstr] == updated_item[:msgstr]) && !(orig_item[:flags] || []).include?("fuzzy")

    # puts "UPDATE of msgid " + orig_item[:msgid].first
    # puts "  old " + orig_item[:msgstr].first
    # puts "  new " + updated_item[:msgstr].first

    update_meta_info   = true
    orig_item[:msgstr] = updated_item[:msgstr]

    next if (updated_item[:flags] || []).include?("fuzzy")

    orig_item[:flags].reject! { |flag| flag == "fuzzy" } if orig_item[:flags]
    orig_item.delete(:suggestions)
  end

  # update_meta_info = true

  return orig_items unless update_meta_info

  orig_meta          = orig_items.first[:comments]
  updated_meta       = updated_items.first[:comments].join("")
  headers_to_update  = %w{PO-Revision-Date Last-Translator Language-Team Plural-Forms}
  headers_to_update += options[:headers_to_update] || []

  headers_to_update.each { |key| replace_po_meta_info orig_meta, updated_meta, key }

  orig_items
end

def transifex_pull_and_merge resource, language
  po_file = resource == "programs" ? "po/#{language}.po" : "doc/man/po4a/po/#{language}.po"

  runq_git po_file, "checkout HEAD -- #{po_file}"

  orig_items = read_po(po_file)

  runq "tx pull", po_file, "tx pull -f -r mkvtoolnix.#{resource} -l #{language} > /dev/null"

  runq_code "merge", :target => po_file do
    transifex_items = read_po(po_file)
    merged_items    = merge_po orig_items, transifex_items
    fixed_items     = fix_po_msgstr_plurals merged_items

    write_po po_file, fixed_items
  end
end

def transifex_remove_fuzzy_and_push resource, language
  po_file          = resource == "programs" ? "po/#{language}.po" : "doc/man/po4a/po/#{language}.po"
  po_file_no_fuzzy = Tempfile.new("mkvtoolnix-rake-po-no-fuzzy")

  runq_git po_file, "checkout HEAD -- #{po_file}"

  runq "msgattrib", po_file, "msgattrib --no-fuzzy --output=#{po_file_no_fuzzy.path} #{po_file}"

  IO.write(po_file, IO.read(po_file_no_fuzzy))

  normalize_po po_file

  runq "tx push",  po_file, "tx push -t -f -r mkvtoolnix.#{resource} -l #{language} > /dev/null"

  runq_git po_file, "checkout HEAD -- #{po_file}"
end

def create_new_po dir
  %w{LANGUAGE EMAIL}.each { |e| fail "Variable '#{e}' is not set" if ENV[e].blank? }

  language = ENV['LANGUAGE']
  locale   = look_up_iso_639_1 language

  puts_action "create", :target => "#{dir}/#{locale}.po"
  File.open "#{dir}/#{locale}.po", "w" do |out|
    now      = Time.now
    email    = ENV['EMAIL']
    email    = "YOUR NAME <#{email}>" unless /</.match(email)
    header   = <<EOT
# translation of mkvtoolnix.pot to #{language}
# Copyright (C) #{now.year} Moritz Bunkus
# This file is distributed under the same license as the MKVToolNix package.
#
msgid ""
EOT

    content = IO.
      readlines("#{dir}/mkvtoolnix.pot").
      join("").
      gsub(/\A.*?msgid ""\n/m, header).
      gsub(/^"PO-Revision-Date:.*?$/m, %{"PO-Revision-Date: #{now.strftime('%Y-%m-%d %H:%M%z')}\\n"}).
      gsub(/^"Last-Translator:.*?$/m,  %{"Last-Translator: #{email}\\n"}).
      gsub(/^"Language-Team:.*?$/m,    %{"Language-Team: #{language} <moritz@bunkus.org>\\n"}).
      gsub(/^"Language: \\n"$/,        %{"Language: #{locale}\\n"})

    out.puts content
  end

  puts "Remember to look up the plural forms in this document:\nhttp://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html"
end