lib/i18n_data/live_data_provider.rb
# frozen_string_literal: true require 'json' require 'simple_po_parser' module I18nData # fetches data online from debian git module LiveDataProvider extend self JSON_CODES = { countries: 'data/iso_3166-1.json', languages: 'data/iso_639-2.json' }.freeze TRANSLATIONS = { countries: 'iso_3166-1/', languages: 'iso_639-2/' }.freeze REPO = "https://salsa.debian.org/iso-codes-team/iso-codes.git" CLONE_DEST = "/tmp/i18n_data_iso_clone" def codes(type, language_code) ensure_checkout language_code = language_code.upcase if language_code == 'EN' send("english_#{type}") else translated(type, language_code) end end def clear_cache `rm -rf #{CLONE_DEST}` raise unless $?.success? end private def ensure_checkout unless File.exist?(CLONE_DEST) `git clone #{REPO} #{CLONE_DEST}` raise unless $?.success? end end def translated(type, language_code) @translated ||= {} @translated["#{type}_#{language_code}"] ||= send("alpha_codes_for_#{type}").transform_values do |alpha3| translate(type, alpha3, language_code) || fallback_name(type, alpha3) end end def translate(type, alpha3, to_language_code) translated = translations(type, to_language_code)[alpha3] translated.to_s.empty? ? nil : translated end def translations(type, language_code) @translations ||= {} @translations["#{type}_#{language_code}"] ||= begin # FOO_BAR -> foo_BAR code = language_code.split("_") code[0].downcase! code = code.join("_") begin file_path = "#{CLONE_DEST}/#{TRANSLATIONS[type]}#{code}.po" # norwegian has 2 versions (nb and nn), but only nb is supported, so we have to pick that # https://salsa.debian.org/iso-codes-team/iso-codes/-/tree/main/iso_639-2 file_path = file_path.sub("/no.po", "/nb.po").sub("/nb.po", "/nb_no.po") data = SimplePoParser.parse(file_path) data = data[1..] # Remove the initial info block in the .po file # Prefer the "Common name for" blocks, but fallback to "Name for" blocks common_names = get_po_data(data, 'Common name for') fallback_names = get_po_data(data, 'Name for') fallback_names.merge(common_names) rescue Errno::ENOENT raise( NoTranslationAvailable, "No translation available for #{type} and language code = #{code} (#{$!})" ) end end end def get_po_data(data, extracted_comment_string) # Ignores the 'fuzzy' entries po_entries = data.select do |t| t[:extracted_comment].start_with?(extracted_comment_string) && t[:flag] != 'fuzzy' end # Maps over the alpha3 country code in the 'extracted_comment' # Eg: "Name for GBR" po_entries.map.with_object({}) do |t, translations| alpha3 = t[:extracted_comment][-3..].upcase translation = t[:msgstr] translations[alpha3] = translation.is_a?(Array) ? translation.join : translation end end def alpha_codes_for_countries @alpha_codes_for_countries ||= json(:countries)['3166-1'].each_with_object({}) do |entry, codes| alpha2 = entry['alpha_2']&.upcase alpha3 = entry['alpha_3']&.upcase codes[alpha2] = alpha3 unless alpha2.nil? end end def alpha_codes_for_languages @alpha_codes_for_languages ||= json(:languages)['639-2'].each_with_object({}) do |entry, codes| alpha2 = entry['alpha_2']&.upcase alpha3 = entry['alpha_3']&.upcase codes[alpha2] = alpha3 unless alpha2.nil? end end def english_languages @english_languages ||= begin codes = {} json(:languages)['639-2'].each do |entry| name = entry['name'].to_s code = entry['alpha_2'].to_s.upcase next if code.empty? || name.empty? codes[code] = name end codes end end def english_countries @english_countries ||= begin codes = {} json(:countries)['3166-1'].each do |entry| name = (entry['common_name'] || entry['name']).to_s code = entry['alpha_2'].to_s.upcase codes[code] = name end codes end end def fallback_name(type, alpha3) send("english_#{type}")[send("alpha_codes_for_#{type}").invert[alpha3]] end def get(url) File.read("#{CLONE_DEST}/#{url}") end def json(type) JSON.parse(get(JSON_CODES[type])) end end end