lib/gettext/po_entry.rb in gettext-3.0.0 vs lib/gettext/po_entry.rb in gettext-3.0.1

- old
+ new

@@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com> +# Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com> # Copyright (C) 2010 masone (Christian Felder) <ema@rh-productions.ch> # Copyright (C) 2009 Masao Mutoh # # License: Ruby's or LGPL # @@ -17,10 +17,12 @@ # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +require "gettext/po_format" + module GetText class ParseError < StandardError end # Contains data related to the expression or sentence that @@ -43,44 +45,10 @@ :plural => [:msgid, :msgid_plural, :separator, :msgstr], :msgctxt => [:msgctxt, :msgid, :msgstr], :msgctxt_plural => [:msgctxt, :msgid, :msgid_plural, :msgstr] } - TRANSLATOR_COMMENT_MARK = "# " - EXTRACTED_COMMENT_MARK = "#." - FLAG_MARK = "#," - PREVIOUS_COMMENT_MARK = "#|" - REFERENCE_COMMENT_MARK = "#:" - - class << self - def escape(string) - string.gsub(/([\\"\t\n])/) do - special_character = $1 - case special_character - when "\t" - "\\t" - when "\n" - "\\n" - else - "\\#{special_character}" - end - end - end - end - - @@max_line_length = 70 - - # Sets the max line length. - def self.max_line_length=(len) - @@max_line_length = len - end - - # Gets the max line length. - def self.max_line_length - @@max_line_length - end - # Required attr_reader :type # :normal, :plural, :msgctxt, :msgctxt_plural attr_accessor :msgid attr_accessor :msgstr # Options @@ -108,25 +76,19 @@ @msgstr = nil end # Support for extracted comments. Explanation s. # http://www.gnu.org/software/gettext/manual/gettext.html#Names + # @return [void] def add_comment(new_comment) if (new_comment and ! new_comment.empty?) @extracted_comment ||= "" @extracted_comment << "\n" unless @extracted_comment.empty? @extracted_comment << new_comment end - to_s end - # Returns a parameter representation suitable for po-files - # and other purposes. - def escaped(param_name) - escape(send(param_name)) - end - # Checks if the self has same attributes as other. def ==(other) not other.nil? and type == other.type and msgid == other.msgid and @@ -159,224 +121,266 @@ # Merges two translation targets with the same msgid and returns the merged # result. If one is declared as plural and the other not, then the one # with the plural wins. def merge(other) return self unless other - raise ParseError, "Translation targets do not match: \n" \ - " self: #{self.inspect}\n other: '#{other.inspect}'" unless self.mergeable?(other) - if other.msgid_plural && !self.msgid_plural + unless mergeable?(other) + message = "Translation targets do not match: \n" + + " self: #{self.inspect}\n other: '#{other.inspect}'" + raise ParseError, message + end + if other.msgid_plural && !msgid_plural res = other - unless (res.references.include? self.references[0]) - res.references += self.references - res.add_comment(self.extracted_comment) + unless res.references.include?(references[0]) + res.references += references + res.add_comment(extracted_comment) end else res = self - unless (res.references.include? other.references[0]) + unless res.references.include?(other.references[0]) res.references += other.references res.add_comment(other.extracted_comment) end end res end - # Output the po entry for the po-file. - def to_s + # Format the po entry in PO format. + # + # @param [Hash] options + # @option options (see Formatter#initialize) + def to_s(options={}) raise(NoMsgidError, "msgid is nil.") unless @msgid - str = "" - # extracted comments - if @msgid == :last - return format_obsolete_comment(comment) - end + formatter = Formatter.new(self, options) + formatter.format + end - str << format_translator_comment - str << format_extracted_comment - str << format_reference_comment - str << format_flag_comment - str << format_previous_comment + # Returns true if the type is kind of msgctxt. + def msgctxt? + [:msgctxt, :msgctxt_plural].include?(@type) + end - # msgctxt, msgid, msgstr - if msgctxt? - if @msgctxt.nil? - no_msgctxt_message = "This POEntry is a kind of msgctxt " + - "but the msgctxt property is nil. " + - "msgid: #{msgid}" - raise(NoMsgctxtError, no_msgctxt_message) + # Returns true if the type is kind of plural. + def plural? + [:plural, :msgctxt_plural].include?(@type) + end + + def [](number) + param = @param_type[number] + raise ParseError, 'no more string parameters expected' unless param + send param + end + + private + + # sets or extends the value of a translation target params like msgid, + # msgctxt etc. + # param is symbol with the name of param + # value - new value + def set_value(param, value) + send "#{param}=", (send(param) || '') + value + end + + class Formatter + class << self + def escape(string) + return "" if string.nil? + + string.gsub(/([\\"\t\n])/) do + special_character = $1 + case special_character + when "\t" + "\\t" + when "\n" + "\\n" + else + "\\#{special_character}" + end + end end - str << "msgctxt " << format_message(msgctxt) end - str << "msgid " << format_message(msgid) - if plural? - if @msgid_plural.nil? - no_plural_message = "This POEntry is a kind of plural " + - "but the msgid_plural property is nil. " + - "msgid: #{msgid}" - raise(NoMsgidPluralError, no_plural_message) + include POFormat + + DEFAULT_MAX_LINE_WIDTH = 78 + + # @param [POEntry] entry The entry to be formatted. + # @param [Hash] options + # @option options [Bool] :include_reference_comment (true) + # Includes reference comments in formatted string if true. + # @option options [Integer] :max_line_width (78) + # Wraps long lines that is longer than the `:max_line_width`. + # Don't break long lines if `:max_line_width` is less than 0 + # such as `-1`. + def initialize(entry, options={}) + @entry = entry + @options = fill_default_option_values(options) + end + + def format + # extracted comments + if @entry.msgid == :last + return format_obsolete_comment(@entry.comment) end - str << "msgid_plural " << format_message(msgid_plural) + str = "" + str << format_translator_comment + str << format_extracted_comment + if @options[:include_reference_comment] + str << format_reference_comment + end + str << format_flag_comment + str << format_previous_comment - if msgstr.nil? - str << "msgstr[0] \"\"\n" - str << "msgstr[1] \"\"\n" - else - msgstrs = msgstr.split("\000", -1) - msgstrs.each_with_index do |msgstr, index| - str << "msgstr[#{index}] " << format_message(msgstr) + # msgctxt, msgid, msgstr + if @entry.msgctxt? + if @entry.msgctxt.nil? + no_msgctxt_message = "This POEntry is a kind of msgctxt " + + "but the msgctxt property is nil. " + + "msgid: #{@entry.msgid}" + raise(NoMsgctxtError, no_msgctxt_message) end + str << "msgctxt " << format_message(@entry.msgctxt) end - else - str << "msgstr " - str << format_message(msgstr) - end - str - end - def format_translator_comment - format_comment("#", translator_comment) - end + str << "msgid " << format_message(@entry.msgid) + if @entry.plural? + if @entry.msgid_plural.nil? + no_plural_message = "This POEntry is a kind of plural " + + "but the msgid_plural property is nil. " + + "msgid: #{@entry.msgid}" + raise(NoMsgidPluralError, no_plural_message) + end - def format_extracted_comment - format_comment(EXTRACTED_COMMENT_MARK, extracted_comment) - end + str << "msgid_plural " << format_message(@entry.msgid_plural) - def format_reference_comment - max_line_length = 70 - formatted_reference = "" - if not references.nil? and not references.empty? - formatted_reference << REFERENCE_COMMENT_MARK - line_size = 2 - references.each do |reference| - if line_size + reference.size > max_line_length - formatted_reference << "\n" - formatted_reference << "#{REFERENCE_COMMENT_MARK} #{reference}" - line_size = 3 + reference.size + if @entry.msgstr.nil? + str << "msgstr[0] \"\"\n" + str << "msgstr[1] \"\"\n" else - formatted_reference << " #{reference}" - line_size += 1 + reference.size + msgstrs = @entry.msgstr.split("\000", -1) + msgstrs.each_with_index do |msgstr, index| + str << "msgstr[#{index}] " << format_message(msgstr) + end end + else + str << "msgstr " + str << format_message(@entry.msgstr) end + str + end - formatted_reference << "\n" + private + def fill_default_option_values(options) + options = options.dup + if options[:include_reference_comment].nil? + options[:include_reference_comment] = true + end + options[:max_line_width] ||= DEFAULT_MAX_LINE_WIDTH + options end - formatted_reference - end - def format_flag_comment - format_comment(FLAG_MARK, flag) - end + def format_translator_comment + format_comment("#", @entry.translator_comment) + end - def format_previous_comment - format_comment(PREVIOUS_COMMENT_MARK, previous) - end + def format_extracted_comment + format_comment(EXTRACTED_COMMENT_MARK, @entry.extracted_comment) + end - def format_comment(mark, comment) - return "" if comment.nil? + def format_reference_comment + max_line_width = @options[:max_line_width] + formatted_reference = "" + if not @entry.references.nil? and not @entry.references.empty? + formatted_reference << REFERENCE_COMMENT_MARK + line_width = 2 + @entry.references.each do |reference| + if max_line_width > 0 and + line_width + reference.size > max_line_width + formatted_reference << "\n" + formatted_reference << "#{REFERENCE_COMMENT_MARK} #{reference}" + line_width = 3 + reference.size + else + formatted_reference << " #{reference}" + line_width += 1 + reference.size + end + end - formatted_comment = "" - comment.each_line do |comment_line| - if comment_line == "\n" - formatted_comment << "#{mark}\n" - else - formatted_comment << "#{mark} #{comment_line.strip}\n" + formatted_reference << "\n" end + formatted_reference end - formatted_comment - end - def format_obsolete_comment(comment) - mark = "#~" - return "" if comment.nil? + def format_flag_comment + format_comment(FLAG_MARK, @entry.flag) + end - formatted_comment = "" - comment.each_line do |comment_line| - if /\A#[^~]/ =~ comment_line or comment_line.start_with?(mark) - formatted_comment << comment_line - elsif comment_line == "\n" - formatted_comment << "\n" - else - formatted_comment << "#{mark} #{comment_line.strip}\n" - end + def format_previous_comment + format_comment(PREVIOUS_COMMENT_MARK, @entry.previous) end - formatted_comment - end - def format_message(message) - formatted_message = "" - if not message.nil? and message.include?("\n") - formatted_message << "\"\"\n" - message.each_line.each do |line| - formatted_message << "\"#{escape(line)}\"\n" + def format_comment(mark, comment) + return "" if comment.nil? + + formatted_comment = "" + comment.each_line do |comment_line| + if comment_line == "\n" + formatted_comment << "#{mark}\n" + else + formatted_comment << "#{mark} #{comment_line.strip}\n" + end end - else - formatted_message << "\"#{escape(message)}\"\n" + formatted_comment end - formatted_message - end - # Returns true if the type is kind of msgctxt. - def msgctxt? - [:msgctxt, :msgctxt_plural].include?(@type) - end + def format_obsolete_comment(comment) + mark = "#~" + return "" if comment.nil? - # Returns true if the type is kind of plural. - def plural? - [:plural, :msgctxt_plural].include?(@type) - end + formatted_comment = "" + comment.each_line do |comment_line| + if /\A#[^~]/ =~ comment_line or comment_line.start_with?(mark) + formatted_comment << comment_line + elsif comment_line == "\n" + formatted_comment << "\n" + else + formatted_comment << "#{mark} #{comment_line.strip}\n" + end + end + formatted_comment + end - private + def format_message(message) + return "\"\"\n" if message.nil? - # sets or extends the value of a translation target params like msgid, - # msgctxt etc. - # param is symbol with the name of param - # value - new value - def set_value(param, value) - send "#{param}=", (send(param) || '') + value - end + chunks = wrap_message(message) + formatted_message = "" + formatted_message << "\"\"\n" if chunks.size > 1 + chunks.each do |chunk| + formatted_message << "\"#{escape(chunk)}\"\n" + end + formatted_message + end - def escape(value) - self.class.escape((value || "").gsub(/\r/, "")) - end + def escape(string) + self.class.escape(string) + end - public - # For backward comatibility. This doesn't support "comment". - # ary = [msgid1, "file1:line1", "file2:line"] - def self.new_from_ary(ary) - ary = ary.dup - msgid = ary.shift - references = ary - type = :normal - msgctxt = nil - msgid_plural = nil + def wrap_message(message) + return [message] if message.empty? - if msgid.include? "\004" - msgctxt, msgid = msgid.split(/\004/) - type = :msgctxt - end - if msgid.include? "\000" - ids = msgid.split(/\000/) - msgid = ids[0] - msgid_plural = ids[1] - if type == :msgctxt - type = :msgctxt_plural - else - type = :plural + max_line_width = @options[:max_line_width] + return [message] if max_line_width <= 0 + + chunks = [] + message.each_line do |line| + # TODO: use character width instead of the number of characters + line.scan(/.{1,#{max_line_width}}/m) do |chunk| + chunks << chunk + end end + chunks end - ret = self.new(type) - ret.msgid = msgid - ret.references = references - ret.msgctxt = msgctxt - ret.msgid_plural = msgid_plural - ret end - - def [](number) - param = @param_type[number] - raise ParseError, 'no more string parameters expected' unless param - send param - end end - end