Skip to content

Commit

Permalink
Merge pull request #367 from calabash/feature/launch-simulator-with-l…
Browse files Browse the repository at this point in the history
…ocale-and-language

Launch simulator with locale and language
  • Loading branch information
jmoody committed Jan 21, 2016
2 parents 796cb42 + 18be5b0 commit a56f9d0
Show file tree
Hide file tree
Showing 12 changed files with 707 additions and 2 deletions.
2 changes: 2 additions & 0 deletions lib/run_loop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
require 'run_loop/core_simulator'
require 'run_loop/simctl/plists'
require 'run_loop/template'
require "run_loop/locale"
require "run_loop/language"

module RunLoop

Expand Down
55 changes: 55 additions & 0 deletions lib/run_loop/core_simulator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,61 @@ def self.erase(simulator, options={})
hash
end

# @!visibility private
#
# @param [RunLoop::Device, String] device a simulator UDID, instruments-ready
# name, or a RunLoop::Device.
#
# @param [String] locale_code a locale code
#
# @raise [ArgumentError] if no device can be found matching the UDID or
# instruments-ready name
# @raise [ArgumentError] if device is not a simulator
# @raise [ArgumentError] if locale_code is invalid
def self.set_locale(device, locale_code)
if device.is_a?(RunLoop::Device)
simulator = device
else
simulator = RunLoop::Device.device_with_identifier(device)
end

if simulator.physical_device?
raise ArgumentError,
"The locale cannot be set on physical devices"
end

self.quit_simulator
RunLoop.log_debug("Setting locale to '#{locale_code}'")
simulator.simulator_set_locale(locale_code)
end

# @!visibility private
#
# @param [RunLoop::Device, String] device a simulator UDID, instruments-ready
# name, or a RunLoop::Device
# @param [String] lang_code a language code
#
# @raise [ArgumentError] if no device can be found matching the UDID or
# instruments-ready name
# @raise [ArgumentError] if device is not a simulator
# @raise [ArgumentError] if language_code is invalid
def self.set_language(device, lang_code)
if device.is_a?(RunLoop::Device)
simulator = device
else
simulator = RunLoop::Device.device_with_identifier(device)
end

if simulator.physical_device?
raise ArgumentError,
"The language cannot be set on physical devices"
end

self.quit_simulator
RunLoop.log_debug("Setting preferred language to '#{lang_code}'")
simulator.simulator_set_language(lang_code)
end

# @!visibility private
def self.simulator_pid
@@simulator_pid
Expand Down
97 changes: 97 additions & 0 deletions lib/run_loop/device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class Device
attr_reader :simulator_accessibility_plist_path
attr_reader :simulator_preferences_plist_path
attr_reader :simulator_log_file_path
attr_reader :pbuddy

# Create a new device.
#
Expand Down Expand Up @@ -266,6 +267,14 @@ def simulator_device_plist
end.call
end

# @!visibility private
def simulator_global_preferences_path
@simulator_global_preferences_path ||= lambda do
return nil if physical_device?
File.join(simulator_root_dir, "data/Library/Preferences/.GlobalPreferences.plist")
end.call
end

# @!visibility private
# Is this the first launch of this Simulator?
#
Expand Down Expand Up @@ -393,6 +402,89 @@ def simulator_wait_for_stable_state
end
end

# @!visibility private
#
# Sets the AppleLocale key in the .GlobalPreferences.plist file
#
# @param [String] locale_code a locale code
#
# @return [RunLoop::Locale] a locale instance
#
# @raise [RuntimeError] if this is a physical device
# @raise [ArgumentError] if the locale code is invalid
def simulator_set_locale(locale_code)
if physical_device?
raise RuntimeError, "This method is for Simulators only"
end

locale = RunLoop::Locale.locale_for_code(locale_code, self)

global_plist = simulator_global_preferences_path
pbuddy.plist_set("AppleLocale", "string", locale.code, global_plist)

locale
end

# @!visibility private
#
# Returns the AppleLanguages array in global plist as an array
#
# @return [Array<String>] list of language codes
def simulator_languages
global_plist = simulator_global_preferences_path
out = pbuddy.plist_read("AppleLanguages", global_plist)

# example: "Array {\n en\n en-US\n}"
# I am intentionally punting on this because I don't want
# to track down edge cases until the output of this method
# is actually used.
result = [out]
begin
result = out.strip.gsub(/[\{\}]/, "").split($-0).map do |elm|
elm.strip
end[1..-1]
rescue => e
RunLoop.log_debug("Caught error #{e.message} trying to parse '#{out}'")
end

result
end

# @!visibility private
#
# Sets the first element in the AppleLanguages array to lang_code.
#
# @param [String] lang_code a language code
#
# @return [Array<String>] list of language codes
#
# @raise [RuntimeError] if this is a physical device
# @raise [ArgumentError] if the language code is invalid
def simulator_set_language(lang_code)
if physical_device?
raise RuntimeError, "This method is for Simulators only"
end

if !RunLoop::Language.valid_code_for_device?(lang_code, self)
raise ArgumentError,
"The language code '#{lang_code}' is not valid for this device"
end

global_plist = simulator_global_preferences_path

cmd = [
"PlistBuddy",
"-c",
"Add :AppleLanguages:0 string '#{lang_code}'",
global_plist
]

# RunLoop::PlistBuddy cannot add items to arrays.
xcrun.exec(cmd, {:log_cmd => true})

simulator_languages
end

private

# @!visibility private
Expand Down Expand Up @@ -427,6 +519,11 @@ def xcrun
RunLoop::Xcrun.new
end

# @!visibility private
def pbuddy
RunLoop::PlistBuddy.new
end

# @!visibility private
def detect_state_from_line(line)

Expand Down
59 changes: 59 additions & 0 deletions lib/run_loop/language.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module RunLoop
# @!visibility private
class Language

# @!visibility private
# Is the lang_code valid for this device?
def self.valid_code_for_device?(lang_code, device)
valid_codes = self.codes_for_device(device)
if valid_codes.nil?
false
else
valid_codes.include?(lang_code)
end
end

# @!visibility private
# Returns a list of known language codes for a device based on iOS version
#
# @param [RunLoop::Device] device
#
# @return [Array<String>] an array of language codes for the device
#
# @raise [ArgumentError] if there are no codes for a device version
def self.codes_for_device(device)
major_version = device.version.major
key = RunLoop::Version.new("#{major_version}.0")
LANGUAGE_CODES[key]
end

private

# @!visibility private
#
# Generated with:
#
# NSArray *identifiers = [[NSLocale availableLocaleIdentifiers]
# sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
# NSMutableOrderedSet *set = [NSMutableOrderedSet new];
# for (NSString *localeId in identifiers) {
# NSString *langCode = [NSLocale canonicalLanguageIdentifierFromString:localeId];
# [set addObject:langCode];
# }
#
# for (NSString *langCode in set) {
# printf("\"%s\",", [langCode cStringUsingEncoding:NSUTF8StringEncoding]);
# }
LANGUAGE_CODES = {
RunLoop::Version.new("9.0") => [
"af","af-NA","af-ZA","agq","agq-CM","ak","ak-GH","am","am-ET","ar","ar-001","ar-AE","ar-BH","ar-DJ","ar-DZ","ar-EG","ar-EH","ar-ER","ar-IL","ar-IQ","ar-JO","ar-KM","ar-KW","ar-LB","ar-LY","ar-MA","ar-MR","ar-OM","ar-PS","ar-QA","ar-SA","ar-SD","ar-SO","ar-SS","ar-SY","ar-TD","ar-TN","ar-YE","as","as-IN","asa","asa-TZ","az","az-Cyrl","az-Cyrl-AZ","az-AZ","bas","bas-CM","be","be-BY","bem","bem-ZM","bez","bez-TZ","bg","bg-BG","bm","bm-ML","bn","bn-BD","bn-IN","bo","bo-CN","bo-IN","br","br-FR","brx","brx-IN","bs","bs-Cyrl","bs-Cyrl-BA","bs-BA","ca","ca-AD","ca-ES","ca-FR","ca-IT","cgg","cgg-UG","chr","chr-US","ckb","ckb-IQ","ckb-IR","cs","cs-CZ","cy","cy-GB","da","da-DK","da-GL","dav","dav-KE","de","de-AT","de-BE","de-CH","de-DE","de-LI","de-LU","dje","dje-NE","dsb","dsb-DE","dua","dua-CM","dyo","dyo-SN","dz","dz-BT","ebu","ebu-KE","ee","ee-GH","ee-TG","el","el-CY","el-GR","en","en-001","en-150","en-AD","en-AG","en-AI","en-AL","en-AS","en-AT","en-AU","en-BA","en-BB","en-BE","en-BM","en-BS","en-BW","en-BZ","en-CA","en-CC","en-CH","en-CK","en-CM","en-CX","en-CY","en-CZ","en-DE","en-DG","en-DK","en-DM","en-EE","en-ER","en-ES","en-FI","en-FJ","en-FK","en-FM","en-FR","en-GB","en-GD","en-GG","en-GH","en-GI","en-GM","en-GR","en-GU","en-GY","en-HK","en-HR","en-HU","en-IE","en-IL","en-IM","en-IN","en-IO","en-IS","en-IT","en-JE","en-JM","en-KE","en-KI","en-KN","en-KY","en-LC","en-LR","en-LS","en-LT","en-LU","en-LV","en-ME","en-MG","en-MH","en-MO","en-MP","en-MS","en-MT","en-MU","en-MW","en-MY","en-NA","en-NF","en-NG","en-NL","en-NO","en-NR","en-NU","en-NZ","en-PG","en-PH","en-PK","en-PL","en-PN","en-PR","en-PT","en-PW","en-RO","en-RU","en-RW","en-SB","en-SC","en-SD","en-SE","en-SG","en-SH","en-SI","en-SK","en-SL","en-SS","en-SX","en-SZ","en-TC","en-TK","en-TO","en-TR","en-TT","en-TV","en-TZ","en-UG","en-UM","en-US","en-US-POSIX","en-VC","en-VG","en-VI","en-VU","en-WS","en-ZA","en-ZM","en-ZW","eo","es","es-419","es-AR","es-BO","es-CL","es-CO","es-CR","es-CU","es-DO","es-EA","es-EC","es-ES","es-GQ","es-GT","es-HN","es-IC","es-MX","es-NI","es-PA","es-PE","es-PH","es-PR","es-PY","es-SV","es-US","es-UY","es-VE","et","et-EE","eu","eu-ES","ewo","ewo-CM","fa","fa-AF","fa-IR","ff","ff-CM","ff-GN","ff-MR","ff-SN","fi","fi-FI","fil","fil-PH","fo","fo-FO","fr","fr-BE","fr-BF","fr-BI","fr-BJ","fr-BL","fr-CA","fr-CD","fr-CF","fr-CG","fr-CH","fr-CI","fr-CM","fr-DJ","fr-DZ","fr-FR","fr-GA","fr-GF","fr-GN","fr-GP","fr-GQ","fr-HT","fr-KM","fr-LU","fr-MA","fr-MC","fr-MF","fr-MG","fr-ML","fr-MQ","fr-MR","fr-MU","fr-NC","fr-NE","fr-PF","fr-PM","fr-RE","fr-RW","fr-SC","fr-SN","fr-SY","fr-TD","fr-TG","fr-TN","fr-VU","fr-WF","fr-YT","fur","fur-IT","fy","fy-NL","ga","ga-IE","gd","gd-GB","gl","gl-ES","gsw","gsw-CH","gsw-FR","gsw-LI","gu","gu-IN","guz","guz-KE","gv","gv-IM","ha","ha-GH","ha-NE","ha-NG","haw","haw-US","he","he-IL","hi","hi-IN","hr","hr-BA","hr-HR","hsb","hsb-DE","hu","hu-HU","hy","hy-AM","id","id-ID","ig","ig-NG","ii","ii-CN","is","is-IS","it","it-CH","it-IT","it-SM","iu","iu-Cans","iu-Cans-CA","ja","ja-JP","jgo","jgo-CM","jmc","jmc-TZ","ka","ka-GE","kab","kab-DZ","kam","kam-KE","kde","kde-TZ","kea","kea-CV","khq","khq-ML","ki","ki-KE","kk","kk-KZ","kkj","kkj-CM","kl","kl-GL","kln","kln-KE","km","km-KH","kn","kn-IN","ko","ko-KP","ko-KR","kok","kok-IN","ks","ks-Arab","ks-Arab-IN","ksb","ksb-TZ","ksf","ksf-CM","ksh","ksh-DE","kw","kw-GB","ky","ky-KG","lag","lag-TZ","lb","lb-LU","lg","lg-UG","lkt","lkt-US","ln","ln-AO","ln-CD","ln-CF","ln-CG","lo","lo-LA","lt","lt-LT","lu","lu-CD","luo","luo-KE","luy","luy-KE","lv","lv-LV","mas","mas-KE","mas-TZ","mer","mer-KE","mfe","mfe-MU","mg","mg-MG","mgh","mgh-MZ","mgo","mgo-CM","mk","mk-MK","ml","ml-IN","mn","mn-MN","mr","mr-IN","ms","ms-Arab","ms-Arab-BN","ms-Arab-MY","ms-BN","ms-MY","ms-SG","mt","mt-MT","mua","mua-CM","my","my-MM","naq","naq-NA","nb","nb-NO","nb-SJ","nd","nd-ZW","ne","ne-IN","ne-NP","nl","nl-AW","nl-BE","nl-BQ","nl-CW","nl-NL","nl-SR","nl-SX","nmg","nmg-CM","nn","nn-NO","nnh","nnh-CM","nus","nus-SD","nyn","nyn-UG","om","om-ET","om-KE","or","or-IN","os","os-GE","os-RU","pa","pa-Arab","pa-Arab-PK","pa-IN","pl","pl-PL","ps","ps-AF","pt","pt-AO","pt-BR","pt-CV","pt-GW","pt-MO","pt-MZ","pt-PT","pt-ST","pt-TL","qu","qu-BO","qu-EC","qu-PE","rm","rm-CH","rn","rn-BI","ro","ro-MD","ro-RO","rof","rof-TZ","ru","ru-BY","ru-KG","ru-KZ","ru-MD","ru-RU","ru-UA","rw","rw-RW","rwk","rwk-TZ","sah","sah-RU","saq","saq-KE","sbp","sbp-TZ","se","se-FI","se-NO","se-SE","seh","seh-MZ","ses","ses-ML","sg","sg-CF","shi","shi-MA","shi-Tfng","shi-Tfng-MA","si","si-LK","sk","sk-SK","sl","sl-SI","smn","smn-FI","sn","sn-ZW","so","so-DJ","so-ET","so-KE","so-SO","sq","sq-AL","sq-MK","sq-XK","sr","sr-BA","sr-ME","sr-RS","sr-XK","sr-Latn","sr-Latn-BA","sr-Latn-ME","sr-Latn-RS","sr-Latn-XK","sv","sv-AX","sv-FI","sv-SE","sw","sw-CD","sw-KE","sw-TZ","sw-UG","ta","ta-IN","ta-LK","ta-MY","ta-SG","te","te-IN","teo","teo-KE","teo-UG","tg","tg-TJ","th","th-TH","ti","ti-ER","ti-ET","tk","tk-TM","to","to-TO","tr","tr-CY","tr-TR","twq","twq-NE","tzm","tzm-MA","ug","ug-Arab","ug-Arab-CN","uk","uk-UA","ur","ur-IN","ur-PK","uz","uz-Arab","uz-Arab-AF","uz-UZ","uz-Latn","uz-Latn-UZ","vai","vai-Latn","vai-Latn-LR","vai-LR","vi","vi-VN","vun","vun-TZ","wae","wae-CH","xog","xog-UG","yav","yav-CM","yi","yi-001","yo","yo-BJ","yo-NG","zgh","zgh-MA","zh","zh-Hans","zh-Hans-CN","zh-Hans-HK","zh-Hans-MO","zh-Hans-SG","zh-Hant","zh-Hant-HK","zh-Hant-MO","zh-Hant-TW","zu","zu-ZA",
],

RunLoop::Version.new("8.0") => [
"af","af-NA","af-ZA","agq","agq-CM","ak","ak-GH","am","am-ET","ar","ar-001","ar-AE","ar-BH","ar-DJ","ar-DZ","ar-EG","ar-EH","ar-ER","ar-IL","ar-IQ","ar-JO","ar-KM","ar-KW","ar-LB","ar-LY","ar-MA","ar-MR","ar-OM","ar-PS","ar-QA","ar-SA","ar-SD","ar-SO","ar-SS","ar-SY","ar-TD","ar-TN","ar-YE","as","as-IN","asa","asa-TZ","az","az-Cyrl","az-Cyrl-AZ","az-AZ","bas","bas-CM","be","be-BY","bem","bem-ZM","bez","bez-TZ","bg","bg-BG","bm","bm-ML","bn","bn-BD","bn-IN","bo","bo-CN","bo-IN","br","br-FR","brx","brx-IN","bs","bs-Cyrl","bs-Cyrl-BA","bs-BA","ca","ca-AD","ca-ES","ca-FR","ca-IT","cgg","cgg-UG","chr","chr-US","ckb","cs","cs-CZ","cy","cy-GB","da","da-DK","da-GL","dav","dav-KE","de","de-AT","de-BE","de-CH","de-DE","de-LI","de-LU","dje","dje-NE","dua","dua-CM","dyo","dyo-SN","dz","dz-BT","ebu","ebu-KE","ee","ee-GH","ee-TG","el","el-CY","el-GR","en","en-001","en-150","en-AD","en-AG","en-AI","en-AL","en-AS","en-AT","en-AU","en-BA","en-BB","en-BE","en-BM","en-BS","en-BW","en-BZ","en-CA","en-CC","en-CH","en-CK","en-CM","en-CX","en-CY","en-CZ","en-DE","en-DG","en-DK","en-DM","en-EE","en-ER","en-ES","en-FI","en-FJ","en-FK","en-FM","en-FR","en-GB","en-GD","en-GG","en-GH","en-GI","en-GM","en-GU","en-GY","en-HK","en-HR","en-HU","en-IE","en-IM","en-IN","en-IO","en-IS","en-IT","en-JE","en-JM","en-KE","en-KI","en-KN","en-KY","en-LC","en-LR","en-LS","en-LT","en-LU","en-LV","en-ME","en-MG","en-MH","en-MO","en-MP","en-MS","en-MT","en-MU","en-MW","en-NA","en-NF","en-NG","en-NL","en-NO","en-NR","en-NU","en-NZ","en-PG","en-PH","en-PK","en-PL","en-PN","en-PR","en-PT","en-PW","en-RO","en-RU","en-RW","en-SB","en-SC","en-SD","en-SE","en-SG","en-SH","en-SI","en-SK","en-SL","en-SS","en-SX","en-SZ","en-TC","en-TK","en-TO","en-TR","en-TT","en-TV","en-TZ","en-UG","en-UM","en-US","en-US-POSIX","en-VC","en-VG","en-VI","en-VU","en-WS","en-ZA","en-ZM","en-ZW","eo","es","es-419","es-AR","es-BO","es-CL","es-CO","es-CR","es-CU","es-DO","es-EA","es-EC","es-ES","es-GQ","es-GT","es-HN","es-IC","es-MX","es-NI","es-PA","es-PE","es-PH","es-PR","es-PY","es-SV","es-US","es-UY","es-VE","et","et-EE","eu","eu-ES","ewo","ewo-CM","fa","fa-AF","fa-IR","ff","ff-SN","fi","fi-FI","fil","fil-PH","fo","fo-FO","fr","fr-BE","fr-BF","fr-BI","fr-BJ","fr-BL","fr-CA","fr-CD","fr-CF","fr-CG","fr-CH","fr-CI","fr-CM","fr-DJ","fr-DZ","fr-FR","fr-GA","fr-GF","fr-GN","fr-GP","fr-GQ","fr-HT","fr-KM","fr-LU","fr-MA","fr-MC","fr-MF","fr-MG","fr-ML","fr-MQ","fr-MR","fr-MU","fr-NC","fr-NE","fr-PF","fr-PM","fr-RE","fr-RW","fr-SC","fr-SN","fr-SY","fr-TD","fr-TG","fr-TN","fr-VU","fr-WF","fr-YT","ga","ga-IE","gl","gl-ES","gsw","gsw-CH","gsw-LI","gu","gu-IN","guz","guz-KE","gv","gv-IM","ha","ha-GH","ha-NE","ha-NG","haw","haw-US","he","he-IL","hi","hi-IN","hr","hr-BA","hr-HR","hu","hu-HU","hy","hy-AM","id","id-ID","ig","ig-NG","ii","ii-CN","is","is-IS","it","it-CH","it-IT","it-SM","iu","iu-Cans","iu-Cans-CA","ja","ja-JP","jgo","jgo-CM","jmc","jmc-TZ","ka","ka-GE","kab","kab-DZ","kam","kam-KE","kde","kde-TZ","kea","kea-CV","khq","khq-ML","ki","ki-KE","kk","kk-KZ","kkj","kkj-CM","kl","kl-GL","kln","kln-KE","km","km-KH","kn","kn-IN","ko","ko-KP","ko-KR","kok","kok-IN","ks","ks-Arab","ks-Arab-IN","ksb","ksb-TZ","ksf","ksf-CM","kw","kw-GB","ky","ky-KG","lag","lag-TZ","lg","lg-UG","lkt","lkt-US","ln","ln-AO","ln-CD","ln-CF","ln-CG","lo","lo-LA","lt","lt-LT","lu","lu-CD","luo","luo-KE","luy","luy-KE","lv","lv-LV","mas","mas-KE","mas-TZ","mer","mer-KE","mfe","mfe-MU","mg","mg-MG","mgh","mgh-MZ","mgo","mgo-CM","mk","mk-MK","ml","ml-IN","mn","mn-MN","mr","mr-IN","ms","ms-Arab","ms-Arab-BN","ms-Arab-MY","ms-BN","ms-MY","ms-SG","mt","mt-MT","mua","mua-CM","my","my-MM","naq","naq-NA","nb","nb-NO","nb-SJ","nd","nd-ZW","ne","ne-IN","ne-NP","nl","nl-AW","nl-BE","nl-BQ","nl-CW","nl-NL","nl-SR","nl-SX","nmg","nmg-CM","nn","nn-NO","nnh","nnh-CM","nus","nus-SD","nyn","nyn-UG","om","om-ET","om-KE","or","or-IN","pa","pa-Arab","pa-Arab-PK","pa-IN","pl","pl-PL","ps","ps-AF","pt","pt-AO","pt-BR","pt-CV","pt-GW","pt-MO","pt-MZ","pt-PT","pt-ST","pt-TL","rm","rm-CH","rn","rn-BI","ro","ro-MD","ro-RO","rof","rof-TZ","ru","ru-BY","ru-KG","ru-KZ","ru-MD","ru-RU","ru-UA","rw","rw-RW","rwk","rwk-TZ","saq","saq-KE","sbp","sbp-TZ","seh","seh-MZ","ses","ses-ML","sg","sg-CF","shi","shi-MA","shi-Tfng","shi-Tfng-MA","si","si-LK","sk","sk-SK","sl","sl-SI","sn","sn-ZW","so","so-DJ","so-ET","so-KE","so-SO","sq","sq-AL","sq-MK","sq-XK","sr","sr-BA","sr-ME","sr-RS","sr-XK","sr-Latn","sr-Latn-BA","sr-Latn-ME","sr-Latn-RS","sr-Latn-XK","sv","sv-AX","sv-FI","sv-SE","sw","sw-KE","sw-TZ","sw-UG","swc","swc-CD","ta","ta-IN","ta-LK","ta-MY","ta-SG","te","te-IN","teo","teo-KE","teo-UG","tg","tg-TJ","th","th-TH","ti","ti-ER","ti-ET","tk","tk-TM","to","to-TO","tr","tr-CY","tr-TR","twq","twq-NE","tzm","tzm-MA","ug","ug-Arab","ug-Arab-CN","uk","uk-UA","ur","ur-IN","ur-PK","uz","uz-Arab","uz-Arab-AF","uz-UZ","uz-Latn","uz-Latn-UZ","vai","vai-Latn","vai-Latn-LR","vai-LR","vi","vi-VN","vun","vun-TZ","xog","xog-UG","yav","yav-CM","yo","yo-BJ","yo-NG","zgh","zgh-MA","zh","zh-Hans","zh-Hans-CN","zh-Hans-HK","zh-Hans-MO","zh-Hans-SG","zh-Hant","zh-Hant-HK","zh-Hant-MO","zh-Hant-TW","zu","zu-ZA",
]
}.freeze

end
end

Loading

0 comments on commit a56f9d0

Please sign in to comment.