jekyll-l10n 1.0.5

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +94 -0
  4. data/lib/jekyll-l10n/constants.rb +136 -0
  5. data/lib/jekyll-l10n/errors.rb +60 -0
  6. data/lib/jekyll-l10n/extraction/compendium_merger.rb +142 -0
  7. data/lib/jekyll-l10n/extraction/compendium_translator.rb +138 -0
  8. data/lib/jekyll-l10n/extraction/config_loader.rb +114 -0
  9. data/lib/jekyll-l10n/extraction/dom_attribute_extractor.rb +69 -0
  10. data/lib/jekyll-l10n/extraction/dom_text_extractor.rb +89 -0
  11. data/lib/jekyll-l10n/extraction/extractor.rb +153 -0
  12. data/lib/jekyll-l10n/extraction/html_string_extractor.rb +103 -0
  13. data/lib/jekyll-l10n/extraction/logger.rb +48 -0
  14. data/lib/jekyll-l10n/extraction/result_saver.rb +95 -0
  15. data/lib/jekyll-l10n/jekyll/file_sync.rb +110 -0
  16. data/lib/jekyll-l10n/jekyll/generator.rb +106 -0
  17. data/lib/jekyll-l10n/jekyll/localized_page.rb +150 -0
  18. data/lib/jekyll-l10n/jekyll/localized_page_mapper.rb +51 -0
  19. data/lib/jekyll-l10n/jekyll/page_locator.rb +59 -0
  20. data/lib/jekyll-l10n/jekyll/page_writer.rb +120 -0
  21. data/lib/jekyll-l10n/jekyll/post_write_html_reprocessor.rb +118 -0
  22. data/lib/jekyll-l10n/jekyll/post_write_processor.rb +71 -0
  23. data/lib/jekyll-l10n/jekyll/regeneration_checker.rb +123 -0
  24. data/lib/jekyll-l10n/jekyll/url_filter.rb +199 -0
  25. data/lib/jekyll-l10n/po_file/loader.rb +64 -0
  26. data/lib/jekyll-l10n/po_file/manager.rb +160 -0
  27. data/lib/jekyll-l10n/po_file/merger.rb +80 -0
  28. data/lib/jekyll-l10n/po_file/path_builder.rb +42 -0
  29. data/lib/jekyll-l10n/po_file/reader.rb +518 -0
  30. data/lib/jekyll-l10n/po_file/writer.rb +232 -0
  31. data/lib/jekyll-l10n/translation/block_text_extractor.rb +56 -0
  32. data/lib/jekyll-l10n/translation/html_translator.rb +229 -0
  33. data/lib/jekyll-l10n/translation/libre_translator.rb +226 -0
  34. data/lib/jekyll-l10n/translation/page_translation_loader.rb +99 -0
  35. data/lib/jekyll-l10n/translation/translator.rb +179 -0
  36. data/lib/jekyll-l10n/utils/debug_logger.rb +153 -0
  37. data/lib/jekyll-l10n/utils/error_handler.rb +67 -0
  38. data/lib/jekyll-l10n/utils/external_link_icon_preserver.rb +122 -0
  39. data/lib/jekyll-l10n/utils/file_operations.rb +55 -0
  40. data/lib/jekyll-l10n/utils/html_elements.rb +34 -0
  41. data/lib/jekyll-l10n/utils/html_parser.rb +52 -0
  42. data/lib/jekyll-l10n/utils/html_text_utils.rb +131 -0
  43. data/lib/jekyll-l10n/utils/logger_formatter.rb +114 -0
  44. data/lib/jekyll-l10n/utils/page_locales_config.rb +344 -0
  45. data/lib/jekyll-l10n/utils/po_entry_converter.rb +111 -0
  46. data/lib/jekyll-l10n/utils/site_config_accessor.rb +51 -0
  47. data/lib/jekyll-l10n/utils/text_normalizer.rb +47 -0
  48. data/lib/jekyll-l10n/utils/text_validator.rb +35 -0
  49. data/lib/jekyll-l10n/utils/translation_resolver.rb +115 -0
  50. data/lib/jekyll-l10n/utils/url_path_builder.rb +65 -0
  51. data/lib/jekyll-l10n/utils/url_transformer.rb +141 -0
  52. data/lib/jekyll-l10n/utils/xpath_reference_generator.rb +45 -0
  53. data/lib/jekyll-l10n/version.rb +10 -0
  54. data/lib/jekyll-l10n.rb +268 -0
  55. metadata +200 -0
@@ -0,0 +1,518 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "gettext/po"
4
+ require_relative "../utils/file_operations"
5
+
6
+ module Jekyll
7
+ module L10n
8
+ # Parses GNU Gettext PO files into translation hashes.
9
+ #
10
+ # PoFileReader reads and parses PO files in standard GNU Gettext format, supporting
11
+ # multiple parsing modes: simple (msgid -> msgstr), with references (for debugging),
12
+ # and with merge metadata (including fuzzy flags). It handles multi-line strings,
13
+ # comment extraction, and various escape sequences. Both instance-based and
14
+ # class-based APIs are supported for backward compatibility.
15
+ #
16
+ # Key responsibilities:
17
+ # * Parse PO files into translation hashes
18
+ # * Handle msgid/msgstr pairs with continuation lines
19
+ # * Extract and preserve reference comments (file location references)
20
+ # * Extract and preserve fuzzy flags during merging
21
+ # * Parse multi-line strings with proper escaping
22
+ # * Support three modes: simple, with_references, and for_merge
23
+ # * Handle both file paths and inline content strings
24
+ #
25
+ # @example
26
+ # reader = PoFileReader.new('_locales/es.po')
27
+ # simple = reader.parse # { "msgid" => "msgstr" }
28
+ # with_refs = reader.parse_with_references # { "msgid" => { msgstr: "...", reference:
29
+ # "..." } }
30
+ # for_merge = reader.parse_for_merge # { "msgid" => { msgstr: "...", reference:
31
+ # "...", fuzzy: false } }
32
+ # rubocop:disable Metrics/ClassLength
33
+ class PoFileReader
34
+ # rubocop:enable Metrics/ClassLength
35
+ MSGID_PATTERN = %r!^msgid ['"](.*)['"] *$!.freeze unless const_defined?(:MSGID_PATTERN)
36
+ MSGSTR_PATTERN = %r!^msgstr ['"](.*)['"] *$!.freeze unless const_defined?(:MSGSTR_PATTERN)
37
+ NO_REFERENCE = nil unless const_defined?(:NO_REFERENCE)
38
+
39
+ # Initialize a new PoFileReader.
40
+ #
41
+ # Accepts either a file path (if file exists) or inline PO content. Determines
42
+ # which based on whether the path exists in the filesystem. Defaults to nil,
43
+ # which initializes the reader with empty content.
44
+ #
45
+ # @param po_path_or_content [String, nil] File path to PO file or inline content
46
+ # string (defaults to nil)
47
+ def initialize(po_path_or_content = nil)
48
+ # Support both file path and content string
49
+ if po_path_or_content && File.exist?(po_path_or_content.to_s)
50
+ @po_path = po_path_or_content
51
+ @content = nil
52
+ else
53
+ @content = po_path_or_content
54
+ @po_path = nil
55
+ end
56
+ end
57
+
58
+ # Parse PO file into simple translation hash.
59
+ #
60
+ # Returns hash mapping msgid strings to msgstr strings (no metadata).
61
+ #
62
+ # @return [Hash] Simple translation hash { msgid => msgstr }
63
+ def parse
64
+ content = load_content
65
+ process_po_lines_instance(content, false)
66
+ end
67
+
68
+ # Parse PO file with reference comments preserved.
69
+ #
70
+ # Returns hash mapping msgid strings to metadata hashes containing msgstr
71
+ # and reference (file location for debugging).
72
+ #
73
+ # @return [Hash] Translation hash with references { msgid => { msgstr: "...",
74
+ # reference: "..." } }
75
+ def parse_with_references
76
+ content = load_content
77
+ process_po_lines_instance(content, true)
78
+ end
79
+
80
+ # Parse PO file with all metadata for merging.
81
+ #
82
+ # Returns hash mapping msgid strings to metadata hashes containing msgstr,
83
+ # reference, and fuzzy flag. Used when merging with existing translations.
84
+ #
85
+ # @return [Hash] Translation hash with merge metadata { msgid => { msgstr: "...",
86
+ # reference: "...", fuzzy: false } }
87
+ def parse_for_merge
88
+ content = load_content
89
+ process_po_lines_instance(content, :merge)
90
+ end
91
+
92
+ # Parse a PO file (class method, for backward compatibility).
93
+ #
94
+ # @param po_path [String] Path to PO file
95
+ # @return [Hash] Simple translation hash
96
+ def self.parse(po_path)
97
+ new(po_path).parse
98
+ end
99
+
100
+ # Parse a PO file with references (class method, for backward compatibility).
101
+ #
102
+ # @param po_path [String] Path to PO file
103
+ # @return [Hash] Translation hash with references
104
+ def self.parse_with_references(po_path)
105
+ new(po_path).parse_with_references
106
+ end
107
+
108
+ # Parse a PO file for merging (class method, for backward compatibility).
109
+ #
110
+ # @param po_path [String] Path to PO file
111
+ # @return [Hash] Translation hash with merge metadata
112
+ def self.parse_for_merge(po_path)
113
+ new(po_path).parse_for_merge
114
+ end
115
+
116
+ # Backward compatibility wrapper
117
+ def self.process_po_lines(content)
118
+ process_po_lines_internal(content, false)
119
+ end
120
+
121
+ # Backward compatibility wrapper
122
+ def self.process_line(lines, idx, translations)
123
+ process_line_internal(lines, idx, translations, false)
124
+ end
125
+
126
+ def self.msgid_line?(line)
127
+ line.start_with?("msgid ")
128
+ end
129
+
130
+ # Splits PO file content into individual lines
131
+ def self.split_lines(content)
132
+ content.split("\n")
133
+ end
134
+
135
+ # Skips blank lines and comments before processing entries
136
+ def self.skip_blank_and_comments(lines, idx)
137
+ return idx if idx >= lines.length
138
+
139
+ line = lines[idx].strip
140
+ if line.empty? || line.start_with?("#")
141
+ idx + 1
142
+ else
143
+ idx
144
+ end
145
+ end
146
+
147
+ # Unified method for processing msgid/msgstr pairs
148
+ # with optional reference and fuzzy metadata
149
+ # with_mode: false (default, simple format), true (with reference), :merge (with both)
150
+ # rubocop:disable Metrics/ParameterLists, Metrics/AbcSize, Metrics/PerceivedComplexity
151
+ def self.process_msgid_msgstr_pair(lines, start_idx, translations,
152
+ reference: nil, fuzzy: nil, with_mode: false)
153
+ # rubocop:enable Metrics/ParameterLists, Metrics/AbcSize, Metrics/PerceivedComplexity
154
+ # Handle nil sentinel values (from NO_REFERENCE constant)
155
+ reference = nil if reference == NO_REFERENCE
156
+ fuzzy = nil if fuzzy == NO_REFERENCE
157
+
158
+ msgid = extract_msgid_and_continuation(lines, start_idx)
159
+ msgid_value = msgid[:value]
160
+ i = msgid[:next_line]
161
+
162
+ if i < lines.length && lines[i].strip.start_with?("msgstr ")
163
+ msgstr = extract_msgstr_and_continuation(lines, i)
164
+ msgstr_value = msgstr[:value]
165
+ i = msgstr[:next_line]
166
+
167
+ # Always use metadata format if in reference or merge mode
168
+ with_metadata = with_mode == true || with_mode == :merge || !reference.nil? || !fuzzy.nil?
169
+ store_translation(
170
+ translations, msgid_value, msgstr_value,
171
+ :reference => reference, :fuzzy => fuzzy, :with_metadata => with_metadata
172
+ )
173
+ else
174
+ i += 1
175
+ end
176
+
177
+ i
178
+ end
179
+
180
+ # Backward compatibility alias
181
+ # rubocop:disable Metrics/ParameterLists
182
+ def self.process_msgid_msgstr_pair_with_metadata(lines, start_idx, translations,
183
+ reference: nil, fuzzy: nil)
184
+ # rubocop:enable Metrics/ParameterLists
185
+ process_msgid_msgstr_pair(lines, start_idx, translations, :reference => reference,
186
+ :fuzzy => fuzzy, :with_mode => :merge)
187
+ end
188
+
189
+ # Backward compatibility alias
190
+ def self.process_msgid_msgstr_pair_with_reference(lines, start_idx, translations, reference)
191
+ process_msgid_msgstr_pair(
192
+ lines, start_idx, translations,
193
+ :reference => reference, :fuzzy => nil, :with_mode => true
194
+ )
195
+ end
196
+
197
+ # Backward compatibility alias
198
+ # rubocop:disable Metrics/ParameterLists
199
+ def self.process_msgid_msgstr_pair_internal(lines, start_idx, translations,
200
+ reference = nil, fuzzy = nil)
201
+ # rubocop:enable Metrics/ParameterLists
202
+ process_msgid_msgstr_pair(lines, start_idx, translations, :reference => reference,
203
+ :fuzzy => fuzzy)
204
+ end
205
+
206
+ def self.process_po_lines_with_references(content)
207
+ process_po_lines_internal(content, true)
208
+ end
209
+
210
+ def self.process_po_lines_for_merge(content)
211
+ process_po_lines_internal(content, :merge)
212
+ end
213
+
214
+ def self.process_po_lines_internal(content, with_references)
215
+ translations = {}
216
+ lines = split_lines(content)
217
+ i = 0
218
+
219
+ while i < lines.length
220
+ i = case with_references
221
+ when true
222
+ process_line_internal(lines, i, translations, true)
223
+ when :merge
224
+ process_line_internal(lines, i, translations, :merge)
225
+ else
226
+ process_line_internal(lines, i, translations, false)
227
+ end
228
+ end
229
+
230
+ translations
231
+ end
232
+
233
+ # Backward compatibility wrappers
234
+ def self.process_line_with_reference(lines, idx, translations)
235
+ process_line_internal(lines, idx, translations, true)
236
+ end
237
+
238
+ def self.process_line_for_merge(lines, idx, translations)
239
+ process_line_internal(lines, idx, translations, :merge)
240
+ end
241
+
242
+ def self.process_line_internal(lines, idx, translations, with_references)
243
+ return idx + 1 if idx >= lines.length
244
+
245
+ idx = skip_blank_and_comments(lines, idx)
246
+ return idx if idx >= lines.length
247
+
248
+ line = lines[idx].strip
249
+
250
+ if msgid_line?(line)
251
+ process_msgid_with_references(lines, idx, translations, with_references)
252
+ else
253
+ idx + 1
254
+ end
255
+ end
256
+
257
+ def self.process_msgid_with_references(lines, idx, translations, with_references)
258
+ case with_references
259
+ when true
260
+ reference = extract_reference_before_msgid(lines, idx)
261
+ process_msgid_msgstr_pair(
262
+ lines, idx, translations,
263
+ :reference => reference, :fuzzy => nil, :with_mode => true
264
+ )
265
+ when :merge
266
+ reference, fuzzy = extract_reference_and_fuzzy_before_msgid(lines, idx)
267
+ process_msgid_msgstr_pair(
268
+ lines, idx, translations,
269
+ :reference => reference, :fuzzy => fuzzy, :with_mode => :merge
270
+ )
271
+ else
272
+ process_msgid_msgstr_pair(lines, idx, translations, :reference => nil, :fuzzy => nil,
273
+ :with_mode => false)
274
+ end
275
+ end
276
+
277
+ # Unified metadata extraction: extracts reference and optionally fuzzy flag
278
+ def self.extract_metadata_before_msgid(lines, msgid_idx, include_fuzzy: false)
279
+ reference = nil
280
+ fuzzy = false
281
+ comments_end = msgid_idx - 1
282
+
283
+ while comments_end >= 0
284
+ comment_line = lines[comments_end].strip
285
+ break unless comment_line.start_with?("#") || comment_line.empty?
286
+
287
+ reference = extract_reference_from_line(comment_line) || reference
288
+ fuzzy = true if include_fuzzy && fuzzy_line?(comment_line)
289
+
290
+ comments_end -= 1
291
+ end
292
+
293
+ include_fuzzy ? [reference, fuzzy] : reference
294
+ end
295
+
296
+ def self.extract_reference_from_line(comment_line)
297
+ comment_line.sub(%r!^#:\s*!, "").strip if comment_line.start_with?("#:")
298
+ end
299
+
300
+ def self.fuzzy_line?(comment_line)
301
+ comment_line.start_with?("#,") && comment_line.include?("fuzzy")
302
+ end
303
+
304
+ # Backward compatibility wrapper
305
+ def self.extract_reference_before_msgid(lines, msgid_idx)
306
+ extract_metadata_before_msgid(lines, msgid_idx, :include_fuzzy => false)
307
+ end
308
+
309
+ # Backward compatibility wrapper
310
+ def self.extract_reference_and_fuzzy_before_msgid(lines, msgid_idx)
311
+ extract_metadata_before_msgid(lines, msgid_idx, :include_fuzzy => true)
312
+ end
313
+
314
+ def self.extract_msgid_and_continuation(lines, start_idx)
315
+ extract_po_field(lines, start_idx, MSGID_PATTERN)
316
+ end
317
+
318
+ def self.extract_msgstr_and_continuation(lines, start_idx)
319
+ extract_po_field(lines, start_idx, MSGSTR_PATTERN)
320
+ end
321
+
322
+ def self.extract_po_field(lines, start_idx, pattern)
323
+ line = lines[start_idx].strip
324
+
325
+ match = line.match(pattern)
326
+ values = match ? [match[1]] : []
327
+ delimiter = line.include?("'") ? "'" : '"'
328
+
329
+ collect_continuation_lines(lines, start_idx + 1, values, delimiter)
330
+ end
331
+
332
+ def self.collect_continuation_lines(lines, start_idx, values, delimiter)
333
+ idx = start_idx
334
+
335
+ while idx < lines.length
336
+ cont_line = lines[idx].strip
337
+
338
+ break if stop_collecting?(cont_line)
339
+
340
+ break unless continuation_line?(cont_line)
341
+
342
+ unescaped = unescape_string(cont_line[1...-1], delimiter)
343
+ values << unescaped
344
+ idx += 1
345
+
346
+ end
347
+
348
+ combined_value = values.join
349
+
350
+ { :value => combined_value, :next_line => idx }
351
+ end
352
+
353
+ def self.continuation_line?(line)
354
+ (line.start_with?('"') && line.end_with?('"')) ||
355
+ (line.start_with?("'") && line.end_with?("'"))
356
+ end
357
+
358
+ # Unescape PO file string values containing escape sequences.
359
+ #
360
+ # === IMPORTANT: Order Matters for Correctness ===
361
+ # Must unescape escaped quotes BEFORE unescaping backslashes.
362
+ # Why: If we unescape backslashes first, we lose information about which
363
+ # backslashes were part of escape sequences vs. literal text.
364
+ #
365
+ # Example with wrong order (backslash first):
366
+ # Input: "Say \\" Hello" (should be: Say \ Hello with closing quote)
367
+ # Wrong: "\\\\" -> "\\" -> Remove quotes -> "Say \" Hello" (quote not closed!)
368
+ #
369
+ # Correct order (quote first):
370
+ # Input: "Say \\" Hello"
371
+ # Right: "\\" -> " " (two backslashes become one literal backslash) -> "Say \ Hello"
372
+ #
373
+ # Single vs Double quotes matter:
374
+ # - Single quotes: Use \\' and \\\\
375
+ # - Double quotes: Use \\" and \\\\
376
+ def self.unescape_string(str, delimiter)
377
+ if delimiter == "'"
378
+ str.gsub("\\'", "'").gsub("\\\\", "\\")
379
+ else
380
+ str.gsub('\\"', '"').gsub("\\\\", "\\")
381
+ end
382
+ end
383
+
384
+ def self.read_po_file(po_path)
385
+ FileOperations.read_utf8(po_path)
386
+ end
387
+
388
+ # Store translation with optional metadata (reference location and fuzzy flag).
389
+ #
390
+ # === Three Storage Formats ===
391
+ # 1. Simple: { msgid => msgstr }
392
+ # Used during translation lookup - minimal memory, fastest access
393
+ #
394
+ # 2. With Reference: { msgid => { msgstr: "...", reference: "file.html:10" } }
395
+ # Used during extraction - preserves source location for debugging
396
+ #
397
+ # 3. With Merge Metadata: { msgid => { msgstr: "...", reference: "...", fuzzy: false } }
398
+ # Used during merging - tracks fuzzy flag for incomplete translations
399
+ #
400
+ # Why three formats instead of one?
401
+ # - Memory efficiency: Simple format used most often (translation lookup)
402
+ # - Flexibility: Can handle different parsing modes without wasting storage
403
+ # - Backward compatibility: Supports legacy calling conventions
404
+ # rubocop:disable Metrics/ParameterLists
405
+ def self.store_translation(translations, msgid, msgstr, reference: nil, fuzzy: nil,
406
+ with_metadata: false)
407
+ # rubocop:enable Metrics/ParameterLists
408
+ return if msgid.nil? || msgstr.nil? || msgid.empty?
409
+
410
+ translations[msgid] = build_translation_entry(msgstr, reference, fuzzy, with_metadata)
411
+ end
412
+
413
+ def self.build_translation_entry(msgstr, reference, fuzzy, with_metadata)
414
+ # Simple format when no metadata requested and none provided
415
+ return msgstr if !with_metadata && reference.nil? && fuzzy.nil?
416
+
417
+ # Build metadata hash based on what's provided
418
+ entry = { :msgstr => msgstr }
419
+ entry[:reference] = reference unless reference.nil?
420
+ entry[:fuzzy] = fuzzy unless fuzzy.nil?
421
+ entry[:comment] = nil if !fuzzy.nil? || !reference.nil?
422
+ entry
423
+ end
424
+
425
+ # Kept for backward compatibility with existing tests
426
+ # Supports both positional and keyword argument calling styles
427
+ # rubocop:disable Metrics/ParameterLists
428
+ def self.store_translation_internal(translations, msgid, msgstr, reference: nil, fuzzy: nil)
429
+ # rubocop:enable Metrics/ParameterLists
430
+ # Handle nil sentinel values (from NO_REFERENCE constant)
431
+ reference = nil if reference == NO_REFERENCE
432
+ fuzzy = nil if fuzzy == NO_REFERENCE
433
+ store_translation(translations, msgid, msgstr, :reference => reference, :fuzzy => fuzzy)
434
+ end
435
+
436
+ # Backward compatibility wrappers for old method signatures
437
+ def self.store_translation_with_reference(translations, msgid, msgstr, reference:)
438
+ store_translation(translations, msgid, msgstr, :reference => reference, :fuzzy => nil,
439
+ :with_metadata => true)
440
+ end
441
+
442
+ def self.store_translation_with_fuzzy(translations, msgid, msgstr, fuzzy:)
443
+ store_translation(translations, msgid, msgstr, :reference => nil, :fuzzy => fuzzy,
444
+ :with_metadata => true)
445
+ end
446
+
447
+ # rubocop:disable Metrics/ParameterLists
448
+ def self.store_translation_with_reference_and_fuzzy(translations, msgid, msgstr,
449
+ reference:, fuzzy:)
450
+ # rubocop:enable Metrics/ParameterLists
451
+ store_translation(translations, msgid, msgstr, :reference => reference, :fuzzy => fuzzy,
452
+ :with_metadata => true)
453
+ end
454
+
455
+ # Determines if we should stop collecting continuation lines
456
+ def self.stop_collecting?(line)
457
+ line.empty? ||
458
+ line.start_with?("#") ||
459
+ line.start_with?("msgid") ||
460
+ line.start_with?("msgstr")
461
+ end
462
+
463
+ private
464
+
465
+ def load_content
466
+ @content || self.class.read_po_file(@po_path)
467
+ end
468
+
469
+ def process_po_lines_instance(content, with_references)
470
+ translations = {}
471
+ lines = self.class.split_lines(content)
472
+ i = 0
473
+
474
+ while i < lines.length
475
+ i = case with_references
476
+ when true
477
+ process_line_instance(lines, i, translations, true)
478
+ when :merge
479
+ process_line_instance(lines, i, translations, :merge)
480
+ else
481
+ process_line_instance(lines, i, translations, false)
482
+ end
483
+ end
484
+
485
+ translations
486
+ end
487
+
488
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
489
+ def process_line_instance(lines, idx, translations, with_references)
490
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
491
+ return idx + 1 if idx >= lines.length
492
+
493
+ idx = self.class.skip_blank_and_comments(lines, idx)
494
+ return idx if idx >= lines.length
495
+
496
+ line = lines[idx].strip
497
+
498
+ if self.class.msgid_line?(line)
499
+ case with_references
500
+ when true
501
+ reference = self.class.extract_reference_before_msgid(lines, idx)
502
+ self.class.process_msgid_msgstr_pair(lines, idx, translations, :reference => reference,
503
+ :fuzzy => nil, :with_mode => true)
504
+ when :merge
505
+ reference, fuzzy = self.class.extract_reference_and_fuzzy_before_msgid(lines, idx)
506
+ self.class.process_msgid_msgstr_pair(lines, idx, translations, :reference => reference,
507
+ :fuzzy => fuzzy, :with_mode => :merge)
508
+ else
509
+ self.class.process_msgid_msgstr_pair(lines, idx, translations, :reference => nil,
510
+ :fuzzy => nil, :with_mode => false)
511
+ end
512
+ else
513
+ idx + 1
514
+ end
515
+ end
516
+ end
517
+ end
518
+ end