Class: Jekyll::L10n::PoFileWriter
- Inherits:
-
Object
- Object
- Jekyll::L10n::PoFileWriter
- Defined in:
- lib/jekyll-l10n/po_file/writer.rb
Overview
Writes extraction entries to GNU Gettext PO files.
PoFileWriter serializes extraction entries into standard PO file format with proper escaping, metadata, and optional merging of existing translations. It handles multi-line strings, special characters, fuzzy flags, and reference comments. When merging is enabled, existing translations are preserved while new strings are added or marked as fuzzy.
Key responsibilities:
-
Create PO file entries from extraction data
-
Merge new entries with existing translations
-
Escape special characters and line breaks
-
Format multi-line and long strings properly
-
Add reference comments (file location references)
-
Add fuzzy flags for merge operations
-
Setup PO file headers with encoding information
-
Write UTF-8 encoded output
Class Method Summary collapse
- .add_flag_comment(entry, lines) ⇒ Object
- .add_msgid_msgstr(entry, lines) ⇒ Object
- .add_reference_comment(entry, lines) ⇒ Object
- .add_translator_comments(entry, lines) ⇒ Object
- .create_po_entry(entry, existing_po) ⇒ Object
- .escape_backslashes(value) ⇒ Object
- .escape_po_string(prefix, value) ⇒ Object
- .escape_quotes_and_get_delimiter(escaped) ⇒ Object
- .format_long_string(prefix, delimiter, escaped) ⇒ Object
- .format_multiline_string(prefix, delimiter, escaped) ⇒ Object
- .get_entries_list(po_file) ⇒ Object
- .header(locale) ⇒ Object
- .merge_entries_preserving_translations(po_file, entries, existing_po) ⇒ Object
- .serialize_po_entry(entry, lines) ⇒ Object
- .serialize_po_file(po_file) ⇒ Object
- .set_po_entry_msgstr(po_entry, existing_entry, new_msgstr) ⇒ Object
- .setup_po_header(po_file, locale) ⇒ Object
-
.write(po_path, entries, locale, skip_merge: false) ⇒ Boolean
Write entries to a PO file.
Class Method Details
.add_flag_comment(entry, lines) ⇒ Object
147 148 149 150 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 147 def self.add_flag_comment(entry, lines) flags = entry.flag.to_s.strip lines << "#, #{flags}" unless flags.empty? end |
.add_msgid_msgstr(entry, lines) ⇒ Object
158 159 160 161 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 158 def self.add_msgid_msgstr(entry, lines) lines << escape_po_string('msgid', entry.msgid) lines << escape_po_string('msgstr', entry.msgstr.to_s) end |
.add_reference_comment(entry, lines) ⇒ Object
152 153 154 155 156 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 152 def self.add_reference_comment(entry, lines) return unless entry.extracted_comment && !entry.extracted_comment.empty? lines << "#: #{entry.extracted_comment}" end |
.add_translator_comments(entry, lines) ⇒ Object
139 140 141 142 143 144 145 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 139 def self.add_translator_comments(entry, lines) return unless entry.translator_comment && !entry.translator_comment.empty? entry.translator_comment.split("\n").each do |comment_line| lines << "# #{comment_line}" unless comment_line.empty? end end |
.create_po_entry(entry, existing_po) ⇒ Object
85 86 87 88 89 90 91 92 93 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 85 def self.create_po_entry(entry, existing_po) po_entry = ::GetText::POEntry.new(:normal) po_entry.msgid = entry[:msgid] existing_entry = existing_po[entry[:msgid]] set_po_entry_msgstr(po_entry, existing_entry, entry[:msgstr]) po_entry end |
.escape_backslashes(value) ⇒ Object
191 192 193 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 191 def self.escape_backslashes(value) value.gsub('\\', '\\\\') end |
.escape_po_string(prefix, value) ⇒ Object
176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 176 def self.escape_po_string(prefix, value) value = value.strip escaped = escape_backslashes(value) delimiter, escaped = escape_quotes_and_get_delimiter(escaped) if escaped.include?("\n") format_multiline_string(prefix, delimiter, escaped) elsif escaped.length < Jekyll::L10n::Constants::PO_SHORT_LINE_LENGTH "#{prefix} #{delimiter}#{escaped}#{delimiter}" else format_long_string(prefix, delimiter, escaped) end end |
.escape_quotes_and_get_delimiter(escaped) ⇒ Object
195 196 197 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 195 def self.escape_quotes_and_get_delimiter(escaped) ['"', escaped.gsub('"', '\\"')] end |
.format_long_string(prefix, delimiter, escaped) ⇒ Object
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 210 def self.format_long_string(prefix, delimiter, escaped) lines = ["#{prefix} #{delimiter}#{delimiter}"] i = 0 while i < escaped.length chunk = escaped[i...(i + Jekyll::L10n::Constants::PO_LINE_LENGTH)] # Never split an escape sequence: if chunk ends with an odd number of # backslashes, the trailing \ is the first byte of \" — pull it back # so it stays with its partner " in the next chunk. trailing_backslashes = chunk[/\\*\z/].length chunk = chunk[0...-1] if trailing_backslashes.odd? lines << "#{delimiter}#{chunk}#{delimiter}" i += chunk.length end lines.join("\n") end |
.format_multiline_string(prefix, delimiter, escaped) ⇒ Object
199 200 201 202 203 204 205 206 207 208 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 199 def self.format_multiline_string(prefix, delimiter, escaped) lines = ["#{prefix} #{delimiter}#{delimiter}"] escaped.split("\n").each do |line| next if line.strip.empty? lines << "#{delimiter}#{line}\\n#{delimiter}" end lines << "#{delimiter}#{delimiter}" if lines.length == 1 lines.join("\n") end |
.get_entries_list(po_file) ⇒ Object
163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 163 def self.get_entries_list(po_file) begin po_file.entries rescue StandardError # entries method failed - try values as fallback po_file.values end rescue StandardError => e # Both methods failed - log and return empty Jekyll.logger.warn 'Localization', "Could not retrieve PO entries: #{e.class}" [] end |
.header(locale) ⇒ Object
104 105 106 107 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 104 def self.header(locale) header_str = "Language: #{locale}\nMIME-Version: 1.0\nContent-Type: text/plain; " "#{header_str}charset=UTF-8\nContent-Transfer-Encoding: 8bit\n" end |
.merge_entries_preserving_translations(po_file, entries, existing_po) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 65 def self.merge_entries_preserving_translations(po_file, entries, existing_po) errors = [] entries.each do |entry| po_entry = create_po_entry(entry, existing_po) po_entry.add_comment(entry[:reference]) if entry[:reference] po_file[entry[:msgid]] = po_entry rescue StandardError => e truncate_length = Jekyll::L10n::Constants::LOG_TRUNCATE_LONG errors << "Error adding '#{entry[:msgid][0..truncate_length]}': " \ "#{e.class} - #{e.}" end return unless errors.any? Jekyll.logger.warn 'Localization', "#{errors.length} errors during merging: #{errors.join(', ')}" end |
.serialize_po_entry(entry, lines) ⇒ Object
131 132 133 134 135 136 137 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 131 def self.serialize_po_entry(entry, lines) add_translator_comments(entry, lines) add_flag_comment(entry, lines) add_reference_comment(entry, lines) add_msgid_msgstr(entry, lines) lines << '' end |
.serialize_po_file(po_file) ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 116 def self.serialize_po_file(po_file) lines = [] entries_list = get_entries_list(po_file) entries_list.each do |entry| serialize_po_entry(entry, lines) end lines.join("\n") rescue StandardError => e error_msg = "DEBUG serialize_po_file: Error iterating entries: #{e.class} - #{e.}" Jekyll.logger.info 'Localization', error_msg '' end |
.set_po_entry_msgstr(po_entry, existing_entry, new_msgstr) ⇒ Object
95 96 97 98 99 100 101 102 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 95 def self.set_po_entry_msgstr(po_entry, existing_entry, new_msgstr) if existing_entry.is_a?(Hash) po_entry.msgstr = existing_entry[:msgstr] || '' po_entry.flag = 'fuzzy' if existing_entry[:fuzzy] else po_entry.msgstr = existing_entry || (new_msgstr || '') end end |
.setup_po_header(po_file, locale) ⇒ Object
109 110 111 112 113 114 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 109 def self.setup_po_header(po_file, locale) header_entry = ::GetText::POEntry.new(:normal) header_entry.msgid = '' header_entry.msgstr = header(locale) po_file[''] = header_entry end |
.write(po_path, entries, locale, skip_merge: false) ⇒ Boolean
Write entries to a PO file.
Creates or updates a PO file with the provided entries. If file exists and skip_merge is false, existing translations are merged (preserved while new entries added). Sets proper PO header with language and UTF-8 encoding.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/jekyll-l10n/po_file/writer.rb', line 48 def self.write(po_path, entries, locale, skip_merge: false) existing_po = if skip_merge || !File.exist?(po_path) {} else PoFileReader.parse_for_merge(po_path) end po_file = ::GetText::PO.new setup_po_header(po_file, locale) merge_entries_preserving_translations(po_file, entries, existing_po) output = serialize_po_file(po_file) FileOperations.write_utf8(po_path, output) true end |