-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce Locale helper models #296
Draft
AliSoftware
wants to merge
10
commits into
trunk
Choose a base branch
from
locales-helper
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
703e5b2
Initial implementation for Locale models (WIP)
AliSoftware 0bcd961
Fix rubocop violations
AliSoftware 6a883ad
Split specs in 2 files + use described_class
AliSoftware b67733a
Improve YARD documention
AliSoftware fdb429f
Introduce a couple of ios/app_store values for some Locales as a star…
AliSoftware 7a15db8
Add a spec to test locale code formats are consistent
AliSoftware 58188a5
typos
AliSoftware 5e80651
Fix git failures in rspecs
AliSoftware 682acd6
Detailed YARD documentation about the various Locale code standards u…
AliSoftware 8204b59
Rename from_xxx methods with a bang `!`
AliSoftware File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
module Fastlane | ||
# Defines a single Locale with the various locale codes depending on the representation needed. | ||
# | ||
# The various locale codes formats for the various keys can be found as follows: | ||
# | ||
# - glotpress: | ||
# Go to the GP project page (e.g. https://translate.wordpress.org/projects/apps/android/dev/) | ||
# and hover over the link for each locale, locale code is in the URL. | ||
# - android: (`values-*` folder names) | ||
# See https://developer.android.com/guide/topics/resources/providing-resources#AlternativeResources (Scroll to Table 2) | ||
# [ISO639-1 (lowercase)]-r[ISO-3166-alpha-2 (uppercase)], e.g. `zh-rCN` ("Chinese understood in mainland China") | ||
# - google_play: (PlayStore Console, for metadata, release_notes.xml and `fastlane supply`) | ||
# See https://support.google.com/googleplay/android-developer/answer/9844778 (then open "View list of available languages"). | ||
# See also https://github.com/fastlane/fastlane/blob/master/supply/lib/supply/languages.rb | ||
# - ios: (`*.lproj`) | ||
# See https://developer.apple.com/documentation/xcode/choosing-localization-regions-and-scripts#Understand-the-Language-Identifier | ||
# [ISO639-1/ISO639-2 (lowercase)]-[ISO 3166-1 (uppercase region or titlecase script)], e.g. `zh-Hans` ("Simplified Chinese" script) | ||
# - app_store: (AppStoreConnect, for metadata and `fastlane deliver`) | ||
# See https://github.com/fastlane/fastlane/blob/master/deliver/lib/deliver/languages.rb | ||
# | ||
# Links to ISO Standards | ||
# ISO standard portal: https://www.iso.org/obp/ui/#search | ||
# ISO 639-1: https://www.loc.gov/standards/iso639-2/php/code_list.php | ||
# ISO-3166-alpha2: https://www.iso.org/obp/ui/#iso:pub:PUB500001:en | ||
# | ||
# Notes about region vs script codes in ISO-3166-1 | ||
# `zh-CN` is a locale code - Chinese understood in mainland China | ||
# `zh-Hans` is a language+script code - Chinese written in Simplified Chinese (not just understood in mainland China) | ||
# | ||
Locale = Struct.new(:glotpress, :android, :google_play, :ios, :app_store, keyword_init: true) do | ||
# Returns the Locale with the given glotpress locale code from the list of all known locales (`Locales.all`) | ||
# | ||
# @param [String] The glotpress locale code for the locale to fetch | ||
# @return [Locale] The locale found | ||
# @raise [RuntimeException] if the locale with given glotpress code is unknown | ||
def self.[](code) | ||
Locales[code].first | ||
end | ||
end | ||
end |
137 changes: 137 additions & 0 deletions
137
lib/fastlane/plugin/wpmreleasetoolkit/models/locales.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
require_relative 'locale' | ||
|
||
module Fastlane | ||
# A class with static methods to manipulate lists of locales. | ||
# | ||
# Exposes various `Array<Locale>` lists like all known locales, the Mag16, | ||
# and convenience methods to turn list of Strings into list of Locales. | ||
# | ||
class Locales | ||
################### | ||
## Constants | ||
ALL_KNOWN_LOCALES = [ | ||
Locale.new(glotpress: 'ar', android: 'ar', google_play: 'ar'), | ||
Locale.new(glotpress: 'de', android: 'de', google_play: 'de-DE'), | ||
Locale.new(glotpress: 'en-gb', android: 'en-rGB', google_play: 'en-US'), | ||
Locale.new(glotpress: 'es', android: 'es', google_play: 'es-ES'), | ||
Locale.new(glotpress: 'fr-ca', android: 'fr-rCA', google_play: 'fr-CA'), | ||
Locale.new(glotpress: 'fr', android: 'fr', google_play: 'fr-FR', ios: 'fr-FR', app_store: 'fr-FR'), | ||
Locale.new(glotpress: 'he', android: 'he', google_play: 'iw-IL'), | ||
Locale.new(glotpress: 'id', android: 'id', google_play: 'id'), | ||
Locale.new(glotpress: 'it', android: 'it', google_play: 'it-IT'), | ||
Locale.new(glotpress: 'ja', android: 'ja', google_play: 'ja-JP'), | ||
Locale.new(glotpress: 'ko', android: 'ko', google_play: 'ko-KR'), | ||
Locale.new(glotpress: 'nl', android: 'nl', google_play: 'nl-NL'), | ||
Locale.new(glotpress: 'pl', android: 'pl', google_play: 'pl-PL'), | ||
Locale.new(glotpress: 'pt-br', android: 'pt-rBR', google_play: 'pt-BR', ios: 'pt-BR', app_store: 'pt-BR'), | ||
Locale.new(glotpress: 'ru', android: 'ru', google_play: 'ru-RU'), | ||
Locale.new(glotpress: 'sr', android: 'sr', google_play: 'sr'), | ||
Locale.new(glotpress: 'sv', android: 'sv', google_play: 'sv-SE'), | ||
Locale.new(glotpress: 'th', android: 'th', google_play: 'th'), | ||
Locale.new(glotpress: 'tr', android: 'tr', google_play: 'tr-TR'), | ||
Locale.new(glotpress: 'vi', android: 'vi', google_play: 'vi'), | ||
Locale.new(glotpress: 'zh-cn', android: 'zh-rCN', google_play: 'zh-CN', ios: 'zh-Hans', app_store: 'zh-Hans'), | ||
Locale.new(glotpress: 'zh-tw', android: 'zh-rTW', google_play: 'zh-TW', ios: 'zh-Hant', app_store: 'zh-Hant'), | ||
Locale.new(glotpress: 'az', android: 'az'), | ||
Locale.new(glotpress: 'el', android: 'el'), | ||
Locale.new(glotpress: 'es-mx', android: 'es-rMX'), | ||
Locale.new(glotpress: 'es-cl', android: 'es-rCL'), | ||
Locale.new(glotpress: 'gd', android: 'gd'), | ||
Locale.new(glotpress: 'hi', android: 'hi'), | ||
Locale.new(glotpress: 'hu', android: 'hu'), | ||
Locale.new(glotpress: 'nb', android: 'nb'), | ||
Locale.new(glotpress: 'pl', android: 'pl'), | ||
Locale.new(glotpress: 'th', android: 'th'), | ||
Locale.new(glotpress: 'uz', android: 'uz'), | ||
Locale.new(glotpress: 'zh-tw', android: 'zh-rHK'), | ||
Locale.new(glotpress: 'eu', android: 'eu'), | ||
Locale.new(glotpress: 'ro', android: 'ro'), | ||
Locale.new(glotpress: 'mk', android: 'mk'), | ||
Locale.new(glotpress: 'en-au', android: 'en-rAU'), | ||
Locale.new(glotpress: 'sr', android: 'sr'), | ||
Locale.new(glotpress: 'sk', android: 'sk'), | ||
Locale.new(glotpress: 'cy', android: 'cy'), | ||
Locale.new(glotpress: 'da', android: 'da'), | ||
Locale.new(glotpress: 'bg', android: 'bg'), | ||
Locale.new(glotpress: 'sq', android: 'sq'), | ||
Locale.new(glotpress: 'hr', android: 'hr'), | ||
Locale.new(glotpress: 'cs', android: 'cs'), | ||
Locale.new(glotpress: 'pt-br', android: 'pt-rBR'), | ||
Locale.new(glotpress: 'en-ca', android: 'en-rCA'), | ||
Locale.new(glotpress: 'ms', android: 'ms'), | ||
Locale.new(glotpress: 'es-ve', android: 'es-rVE'), | ||
Locale.new(glotpress: 'gl', android: 'gl'), | ||
Locale.new(glotpress: 'is', android: 'is'), | ||
Locale.new(glotpress: 'es-co', android: 'es-rCO'), | ||
Locale.new(glotpress: 'kmr', android: 'kmr'), | ||
].freeze | ||
|
||
MAG16_GP_CODES = %w[ar de es fr he id it ja ko nl pt-br ru sv tr zh-cn zh-tw].freeze | ||
|
||
################### | ||
## Static Methods | ||
|
||
class << self | ||
# @return [Array<Locale>] Array of all the known locales | ||
# | ||
def all | ||
ALL_KNOWN_LOCALES | ||
end | ||
|
||
# Define from_glotpress(code_or_list), from_android(code_or_list) … methods. | ||
# | ||
# Those can be used in the rare cases where you need to find locales via codes other than the glotpress ones, | ||
# like searching by android locale code(s) or google_play locale code(s). | ||
# In most cases, prefer using the `Locales[…]` method instead (with glotpress locale codes). | ||
# | ||
# @param [Array<String>, String] list of locale codes to search for, or single value for single result | ||
# @return [Array<Locale>, Locale] list of found locales, or single locale if a single value was passed | ||
# @raise [RuntimeException] if at least one of the locale codes was unknown | ||
# | ||
%i[glotpress android google_play ios app_store].each do |key| | ||
define_method("from_#{key}!") { |args| search!(key, args) } | ||
end | ||
|
||
# Return an Array<Locale> based on glotpress locale codes | ||
# | ||
# @note If you need a single locale instead of an `Array<Locale>`, you can use Locale[code] instead of Locales[code] | ||
# | ||
# @param [String..., Array<String>] Arbitrary list of strings, either passed as a single array parameter, or as a vararg list of params | ||
# @return [Array<Locale>] The found locales. | ||
# @raise [RuntimeException] if at least one of the locale codes was unknown | ||
# | ||
def [](*list) | ||
# If we passed a variadic list of Strings, `*list` will make it a single `Array<String>` and we were already good to go. | ||
# But if we passed an Array, `*list` will make it an Array<Array<String>> of one item; taking `list.first` will go back to Array<String>. | ||
list = list.first if list.count == 1 && list.first.is_a?(Array) | ||
from_glotpress!(list) | ||
end | ||
|
||
# Return the subset of the 16 locales most of our apps are localized 100% (the ones we call the "Magnificent 16") | ||
# | ||
# @return [Array<Locale>] List of the Mag16 locales | ||
def mag16 | ||
from_glotpress!(MAG16_GP_CODES) | ||
end | ||
|
||
################### | ||
|
||
private | ||
|
||
# Search the known locales for just the ones having the provided locale code, where the codes are expressed using the standard for the given key | ||
def search!(key, code_or_list) | ||
if code_or_list.is_a?(Array) | ||
code_or_list.map { |code| search!(key, code) } | ||
else # String | ||
raise 'The locale code should not contain spaces. Did you accidentally use `%[]` instead of `%w[]` at call site?' if code_or_list.include?(' ') | ||
|
||
ALL_KNOWN_LOCALES.find { |locale| locale.send(key) == code_or_list } || not_found!(code_or_list, key) | ||
end | ||
end | ||
|
||
def not_found!(code, key) | ||
raise "Unknown locale for #{key} code '#{code}'" | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
require 'spec_helper' | ||
|
||
describe Fastlane::Locale do | ||
it 'returns a single Locale if one was found' do | ||
locale = described_class['fr'] | ||
expect(locale).to be_instance_of(described_class) | ||
expect(locale.glotpress).to eq('fr') | ||
end | ||
|
||
it 'raises if no locale was found for a given code' do | ||
expect do | ||
described_class['invalidcode'] | ||
end.to raise_error(RuntimeError, "Unknown locale for glotpress code 'invalidcode'") | ||
end | ||
|
||
it 'can convert a Locale to a hash' do | ||
h = described_class['fr'].to_h | ||
expect(h).to eq({ glotpress: 'fr', android: 'fr', google_play: 'fr-FR', ios: 'fr-FR', app_store: 'fr-FR' }) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
require 'spec_helper' | ||
|
||
describe Fastlane::Locales do | ||
shared_examples 'from_xxx' do |key, fr_code, pt_code| | ||
let(:method_sym) { "from_#{key}!".to_sym } | ||
|
||
it 'can find a locale from a single code' do | ||
fr_locale = described_class.send(method_sym, fr_code) | ||
expect(fr_locale).to be_instance_of(Fastlane::Locale) | ||
expect(fr_locale.glotpress).to eq('fr') | ||
expect(fr_locale.android).to eq('fr') | ||
expect(fr_locale.google_play).to eq('fr-FR') | ||
end | ||
|
||
it 'can find locales from a multiple codes' do | ||
locales = described_class.send(method_sym, [fr_code, pt_code]) | ||
expect(locales).to be_instance_of(Array) | ||
|
||
expect(locales[0]).to be_instance_of(Fastlane::Locale) | ||
expect(locales[0].glotpress).to eq('fr') | ||
|
||
expect(locales[1]).to be_instance_of(Fastlane::Locale) | ||
expect(locales[1].glotpress).to eq('pt-br') | ||
end | ||
|
||
it 'raises if one of the locale codes passed was not found' do | ||
expect do | ||
described_class.send(method_sym, [fr_code, 'invalidcode', pt_code]) | ||
end.to raise_error(RuntimeError, "Unknown locale for #{key} code 'invalidcode'") | ||
end | ||
end | ||
|
||
describe 'from_glotpress!' do | ||
include_examples 'from_xxx', :glotpress, 'fr', 'pt-br' | ||
end | ||
|
||
describe 'from_android!' do | ||
include_examples 'from_xxx', :android, 'fr', 'pt-rBR' | ||
end | ||
|
||
describe 'from_google_play!' do | ||
include_examples 'from_xxx', :google_play, 'fr-FR', 'pt-BR' | ||
end | ||
|
||
describe 'from_ios!' do | ||
include_examples 'from_xxx', :ios, 'fr-FR', 'pt-BR' | ||
end | ||
|
||
describe 'from_app_store!' do | ||
include_examples 'from_xxx', :app_store, 'fr-FR', 'pt-BR' | ||
end | ||
|
||
describe 'subscript [] operator' do | ||
it 'returns an Array<Locale> even if a single one was passed' do | ||
locales = described_class['fr'] | ||
expect(locales).to be_instance_of(Array) | ||
expect(locales.count).to equal(1) | ||
expect(locales[0].glotpress).to eq('fr') | ||
end | ||
|
||
it 'returns an Array<Locale> if a list of vararg codes was passed' do | ||
locales = described_class['fr', 'pt-br'] | ||
expect(locales).to be_instance_of(Array) | ||
expect(locales.count).to equal(2) | ||
expect(locales[0]).to be_instance_of(Fastlane::Locale) | ||
expect(locales[0].glotpress).to eq('fr') | ||
expect(locales[1]).to be_instance_of(Fastlane::Locale) | ||
expect(locales[1].glotpress).to eq('pt-br') | ||
end | ||
|
||
it 'returns an Array<Locale> if an Array<String> of codes was passed' do | ||
list = %w[fr pt-br] | ||
locales = described_class[list] | ||
expect(locales).to be_instance_of(Array) | ||
expect(locales.count).to equal(2) | ||
expect(locales[0]).to be_instance_of(Fastlane::Locale) | ||
expect(locales[0].glotpress).to eq('fr') | ||
expect(locales[1]).to be_instance_of(Fastlane::Locale) | ||
expect(locales[1].glotpress).to eq('pt-br') | ||
end | ||
end | ||
|
||
it 'has only valid codes for known locales' do | ||
described_class.all.each do |locale| | ||
expect(locale.glotpress || 'xx').to match(/^[a-z]{2,3}(-[a-z]{2})?$/) | ||
expect(locale.android || 'xx-rYY').to match(/^[a-z]{2,3}(-r[A-Z]{2})?$/) | ||
expect(locale.google_play || 'xx-YY').to match(/^[a-z]{2,3}(-[A-Z]{2})?$/) | ||
expect(locale.app_store || 'xx-Yy').to match(/^[a-z]{2,3}(-[A-Za-z]{2,4})?$/) | ||
expect(locale.ios || 'xx-Yy').to match(/^[a-z]{2,3}(-[A-Za-z]{2,4})?$/) | ||
end | ||
end | ||
|
||
it 'returns exactly 16 Mag16 locales' do | ||
expect(described_class.mag16.count).to eq(16) | ||
end | ||
|
||
it 'is easy to do Locale subset intersections' do | ||
mag16_except_pt = described_class.mag16 - described_class['pt-br'] | ||
expect(mag16_except_pt.count).to equal(15) | ||
expect(mag16_except_pt.find { |l| l.glotpress == 'pt-br' }).to be_nil | ||
expect(mag16_except_pt.find { |l| l.glotpress == 'fr' }).not_to be_nil | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WIP: To be completed with
ios:
andapp_store:
values for each locale; we'll also need to double-check all the tuples to ensure we defined the correct ones.