class Twine::Formatters::Android

Constants

LANG_MAPPINGS

Public Instance Methods

can_handle_directory?(path) click to toggle source
# File lib/twine/formatters/android.rb, line 27
def can_handle_directory?(path)
  Dir.entries(path).any? { |item| /^values.*$/.match(item) }
end
can_handle_file?(path) click to toggle source
# File lib/twine/formatters/android.rb, line 31
def can_handle_file?(path)
  path_arr = path.split(File::SEPARATOR)
  return path_arr[path_arr.length - 1] == default_file_name
end
default_file_name() click to toggle source
# File lib/twine/formatters/android.rb, line 36
def default_file_name
  return 'strings.xml'
end
determine_language_given_path(path) click to toggle source
# File lib/twine/formatters/android.rb, line 40
def determine_language_given_path(path)
  path_arr = path.split(File::SEPARATOR)
  path_arr.each do |segment|
    if segment == 'values'
      return 'en'
    else
      # The language is defined by a two-letter ISO 639-1 language code, optionally followed by a two letter ISO 3166-1-alpha-2 region code (preceded by lowercase "r").
      # see http://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources
      match = /^values-([a-z]{2}(-r[a-z]{2})?)$/i.match(segment)
      if match
        lang = match[1]
        lang = LANG_MAPPINGS.fetch(lang, lang)
        lang.sub!('-r', '-')
        return lang
      end
    end
  end

  return
end
escape_value(value) click to toggle source
# File lib/twine/formatters/android.rb, line 206
def escape_value(value)
  # escape double and single quotes, & signs and tags
  value = escape_quotes(value)
  value.gsub!("'", "\\\\'")
  value.gsub!(/&/, '&')
  value.gsub!('<', '&lt;')

  # escape non resource identifier @ signs (http://developer.android.com/guide/topics/resources/accessing-resources.html#ResourcesFromXml)
  resource_identifier_regex = /@(?!([a-z\.]+:)?[a-z+]+\/[a-zA-Z_]+)/   # @[<package_name>:]<resource_type>/<resource_name>
  value.gsub(resource_identifier_regex, '\@')

  value.gsub('strong>', 'b>')
end
extension() click to toggle source
# File lib/twine/formatters/android.rb, line 23
def extension
  '.xml'
end
format_comment(definition, lang) click to toggle source
# File lib/twine/formatters/android.rb, line 176
def format_comment(definition, lang)
  "    <!-- #{definition.comment.gsub('--', '—')} -->\n" if definition.comment
end
format_header(lang) click to toggle source
# File lib/twine/formatters/android.rb, line 131
def format_header(lang)
  "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Android Strings File -->\n<!-- Generated by Twine #{Twine::VERSION} -->\n<!-- Language: #{lang} -->"
end
format_key_value_plural_item(definition, lang) click to toggle source
# File lib/twine/formatters/android.rb, line 188
def format_key_value_plural_item(definition, lang)
  value = definition.translation_for_lang(lang)
  plurals_item_key_value_pattern(format_key(definition.key.dup), format_value(value.dup))
end
format_name() click to toggle source
# File lib/twine/formatters/android.rb, line 19
def format_name
  'android'
end
format_plural(definition, lang) click to toggle source
# File lib/twine/formatters/android.rb, line 184
def format_plural(definition, lang)
  [format_comment(definition, lang), format_key_value_plural_item(definition, lang)].compact.join
end
format_section(section, lang) click to toggle source
# File lib/twine/formatters/android.rb, line 143
def format_section(section, lang)
  definitions = section.definitions.select { |definition| should_include_definition(definition, lang) }
  return if definitions.empty?

  result = ""

  if section.name && section.name.length > 0
    section_header = format_section_header(section)
    result += "\n#{section_header}" if section_header

    # DEAL WITH PLURALS HERE
    if section.is_uncategorized
      definitions.map! { |definition| format_definition(definition, lang) }
      definitions.compact! # remove nil definitions
      definitions.map! { |definition| "\n#{definition}" }  # prepend newline
      result += definitions.join
    else
      result +=  plurals_start_key_value_pattern % { key:section.name }

      definitions.map! { |definition| format_plural(definition, lang) }
      definitions.compact! # remove nil definitions
      definitions.map! { |definition| "\n#{definition}" }  # prepend newline
      result += definitions.join

      result += plurals_end_key_value_pattern
    end
  end
end
format_section_header(section) click to toggle source
# File lib/twine/formatters/android.rb, line 172
def format_section_header(section)
  "    <!-- SECTION: #{section.name} -->"
end
format_sections(twine_file, lang) click to toggle source
# File lib/twine/formatters/android.rb, line 135
def format_sections(twine_file, lang)
  result = '<resources>'

  result += super + "\n"

  result += "</resources>\n"
end
format_value(value) click to toggle source

see developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling however unescaped HTML markup like in “Welcome to Android!” is stripped when retrieved with getString() (stackoverflow.com/questions/9891996/)

# File lib/twine/formatters/android.rb, line 222
def format_value(value)
  value = value.dup

  # capture xliff tags and replace them with a placeholder
  xliff_tags = []
  value.gsub! /<xliff:g.+?<\/xliff:g>/ do
    xliff_tags << $&
    'TWINE_XLIFF_TAG_PLACEHOLDER'
  end

  # escape everything outside xliff tags
  value = escape_value(value)

  # put xliff tags back into place
  xliff_tags.each do |xliff_tag|
    # escape content of xliff tags
    xliff_tag.gsub! /(<xliff:g.*?>)(.*)(<\/xliff:g>)/ do "#{$1}#{escape_value($2)}#{$3}" end
    value.sub! 'TWINE_XLIFF_TAG_PLACEHOLDER', xliff_tag
  end

  # convert placeholders (e.g. %@ -> %s)
  value = convert_placeholders_from_twine_to_android(value)

  # replace beginning and end spaces with \u0020. Otherwise Android strips them.
  value.gsub(/\A *| *\z/) { |spaces| '\u0020' * spaces.length }
  value.gsub('%#s', '%#@')
end
key_value_pattern() click to toggle source
# File lib/twine/formatters/android.rb, line 180
def key_value_pattern
  "    <string name=\"%{key}\">%{value}</string>"
end
output_path_for_language(lang) click to toggle source
# File lib/twine/formatters/android.rb, line 61
def output_path_for_language(lang)
  if lang == 'en'
    "values"
  else
    "values-" + (LANG_MAPPINGS.key(lang) || lang)
  end
end
plurals_end_key_value_pattern() click to toggle source
# File lib/twine/formatters/android.rb, line 202
def plurals_end_key_value_pattern
  "\n    </plurals>"
end
plurals_item_key_value_pattern(key, value) click to toggle source
# File lib/twine/formatters/android.rb, line 197
def plurals_item_key_value_pattern(key, value)
  partitions = key.rpartition(/.__/)
  "        <item quantity=\"" + partitions[partitions.length - 1] + "\">" + value + "</item>"
end
plurals_start_key_value_pattern() click to toggle source
# File lib/twine/formatters/android.rb, line 193
def plurals_start_key_value_pattern
  "\n    <plurals name=\"%{key}\">"
end
read(io, lang) click to toggle source
# File lib/twine/formatters/android.rb, line 79
def read(io, lang)
  document = REXML::Document.new io, :compress_whitespace => %w{ string }

  comment = nil
  document.root.children.each do |child|
    if child.is_a? REXML::Comment
      content = child.string.strip
      comment = content if content.length > 0 and not content.start_with?("SECTION:")

    elsif child.is_a? REXML::Element

      if child.attributes['translatable']
        next
      end

      section = nil
      if child.name == 'plurals'
        key = child.attributes['name']

        if !section_exists(key)
          section = TwineSection.new(key)
          @twine_file.sections.insert(@twine_file.sections.size - 1, section)
        else
          section = get_section(key)
        end

        child.each do |item|
          if item.is_a? REXML::Element
            plural_key = key + '__' + item.attributes['quantity']
            set_translation_for_key(section, plural_key, lang, item.text)
          end
        end
      elsif child.name == 'string'

        if !section_exists('Uncategorized')
          section = TwineSection.new('Uncategorized')
          @twine_file.sections.insert(0, section)
        else
          section = get_section('Uncategorized')
        end

        key = child.attributes['name']

        set_translation_for_key(section, key, lang, child.text)
        set_comment_for_key(key, comment) if comment

        comment = nil
      end
    end
  end
end
set_translation_for_key(section, key, lang, value) click to toggle source
# File lib/twine/formatters/android.rb, line 69
def set_translation_for_key(section, key, lang, value)
  value = CGI.unescapeHTML(value)
  value.gsub!('\\\'', '\'')
  value.gsub!('\\"', '"')
  value = convert_placeholders_from_android_to_twine(value)
  value.gsub!('\@', '@')
  value.gsub!(/(\\u0020)*|(\\u0020)*\z/) { |spaces| ' ' * (spaces.length / 6) }
  super(section, key, lang, value)
end