diff options
-rw-r--r-- | tools/aapt2/cmd/Compile.cpp | 15 | ||||
-rw-r--r-- | tools/aapt2/cmd/Compile.h | 11 | ||||
-rw-r--r-- | tools/aapt2/cmd/Compile_test.cpp | 21 | ||||
-rw-r--r-- | tools/aapt2/compile/PseudolocaleGenerator.cpp | 203 | ||||
-rw-r--r-- | tools/aapt2/compile/PseudolocaleGenerator.h | 15 | ||||
-rw-r--r-- | tools/aapt2/compile/PseudolocaleGenerator_test.cpp | 215 |
6 files changed, 465 insertions, 15 deletions
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index d2ea59958f69..b5c290ec8dad 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -186,7 +186,20 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, // These are created as weak symbols, and are only generated from default // configuration // strings and plurals. - PseudolocaleGenerator pseudolocale_generator; + std::string grammatical_gender_values; + std::string grammatical_gender_ratio; + if (options.pseudo_localize_gender_values) { + grammatical_gender_values = options.pseudo_localize_gender_values.value(); + } else { + grammatical_gender_values = "f,m,n"; + } + if (options.pseudo_localize_gender_ratio) { + grammatical_gender_ratio = options.pseudo_localize_gender_ratio.value(); + } else { + grammatical_gender_ratio = "1.0"; + } + PseudolocaleGenerator pseudolocale_generator(grammatical_gender_values, + grammatical_gender_ratio); if (!pseudolocale_generator.Consume(context, &table)) { return false; } diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h index 14a730a1b1a0..22890fc53d5c 100644 --- a/tools/aapt2/cmd/Compile.h +++ b/tools/aapt2/cmd/Compile.h @@ -35,6 +35,8 @@ struct CompileOptions { std::optional<std::string> res_dir; std::optional<std::string> res_zip; std::optional<std::string> generate_text_symbols_path; + std::optional<std::string> pseudo_localize_gender_values; + std::optional<std::string> pseudo_localize_gender_ratio; std::optional<Visibility::Level> visibility; bool pseudolocalize = false; bool no_png_crunch = false; @@ -76,6 +78,15 @@ class CompileCommand : public Command { AddOptionalFlag("--source-path", "Sets the compiled resource file source file path to the given string.", &options_.source_path); + AddOptionalFlag("--pseudo-localize-gender-values", + "Sets the gender values to pick up for generating grammatical gender strings, " + "gender values should be f, m, or n, which are shortcuts for feminine, " + "masculine and neuter, and split with comma.", + &options_.pseudo_localize_gender_values); + AddOptionalFlag("--pseudo-localize-gender-ratio", + "Sets the ratio of resources to generate grammatical gender strings for. The " + "ratio has to be a float number between 0 and 1.", + &options_.pseudo_localize_gender_ratio); } int Action(const std::vector<std::string>& args) override; diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index 3464a7662c60..8880089d0e20 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -236,9 +236,24 @@ TEST_F(CompilerTest, DoNotTranslateTest) { // The first string (000) is translatable, the second is not // ar-XB uses "\u200F\u202E...\u202C\u200F" std::vector<std::string> expected_translatable = { - "000", "111", // default locale - "[000 one]", // en-XA - "\xE2\x80\x8F\xE2\x80\xAE" "000" "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB + "(F)[000 one]", // en-XA-feminine + "(F)\xE2\x80\x8F\xE2\x80\xAE" + "000" + "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB-feminine + "(M)[000 one]", // en-XA-masculine + "(M)\xE2\x80\x8F\xE2\x80\xAE" + "000" + "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB-masculine + "(N)[000 one]", // en-XA-neuter + "(N)\xE2\x80\x8F\xE2\x80\xAE" + "000" + "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB-neuter + "000", // default locale + "111", // default locale + "[000 one]", // en-XA + "\xE2\x80\x8F\xE2\x80\xAE" + "000" + "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB }; AssertTranslations(this, "foo", expected_translatable); AssertTranslations(this, "foo_donottranslate", expected_translatable); diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index 09a8560f984a..8143052f4376 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -16,11 +16,15 @@ #include "compile/PseudolocaleGenerator.h" +#include <stdint.h> + #include <algorithm> +#include <random> #include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" +#include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" #include "compile/Pseudolocalizer.h" #include "util/Util.h" @@ -293,8 +297,85 @@ class Visitor : public ValueVisitor { Pseudolocalizer localizer_; }; +class GrammaticalGenderVisitor : public ValueVisitor { + public: + std::unique_ptr<Value> value; + std::unique_ptr<Item> item; + + GrammaticalGenderVisitor(android::StringPool* pool, uint8_t grammaticalInflection) + : pool_(pool), grammaticalInflection_(grammaticalInflection) { + } + + void Visit(Plural* plural) override { + CloningValueTransformer cloner(pool_); + std::unique_ptr<Plural> grammatical_gendered = util::make_unique<Plural>(); + for (size_t i = 0; i < plural->values.size(); i++) { + if (plural->values[i]) { + GrammaticalGenderVisitor sub_visitor(pool_, grammaticalInflection_); + plural->values[i]->Accept(&sub_visitor); + if (sub_visitor.item) { + grammatical_gendered->values[i] = std::move(sub_visitor.item); + } else { + grammatical_gendered->values[i] = plural->values[i]->Transform(cloner); + } + } + } + grammatical_gendered->SetSource(plural->GetSource()); + grammatical_gendered->SetWeak(true); + value = std::move(grammatical_gendered); + } + + std::string AddGrammaticalGenderPrefix(const std::string_view& original_string) { + std::string result; + switch (grammaticalInflection_) { + case android::ResTable_config::GRAMMATICAL_GENDER_MASCULINE: + result = std::string("(M)") + std::string(original_string); + break; + case android::ResTable_config::GRAMMATICAL_GENDER_FEMININE: + result = std::string("(F)") + std::string(original_string); + break; + case android::ResTable_config::GRAMMATICAL_GENDER_NEUTER: + result = std::string("(N)") + std::string(original_string); + break; + default: + result = std::string(original_string); + break; + } + return result; + } + + void Visit(String* string) override { + std::string prefixed_string = AddGrammaticalGenderPrefix(std::string(*string->value)); + std::unique_ptr<String> grammatical_gendered = + util::make_unique<String>(pool_->MakeRef(prefixed_string)); + grammatical_gendered->SetSource(string->GetSource()); + grammatical_gendered->SetWeak(true); + item = std::move(grammatical_gendered); + } + + void Visit(StyledString* string) override { + std::string prefixed_string = AddGrammaticalGenderPrefix(std::string(string->value->value)); + android::StyleString new_string; + new_string.str = std::move(prefixed_string); + for (const android::StringPool::Span& span : string->value->spans) { + new_string.spans.emplace_back(android::Span{*span.name, span.first_char, span.last_char}); + } + std::unique_ptr<StyledString> grammatical_gendered = + util::make_unique<StyledString>(pool_->MakeRef(new_string)); + grammatical_gendered->SetSource(string->GetSource()); + grammatical_gendered->SetWeak(true); + item = std::move(grammatical_gendered); + } + + private: + DISALLOW_COPY_AND_ASSIGN(GrammaticalGenderVisitor); + android::StringPool* pool_; + uint8_t grammaticalInflection_; +}; + ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base, - Pseudolocalizer::Method m) { + Pseudolocalizer::Method m, + uint8_t grammaticalInflection) { ConfigDescription modified = base; switch (m) { case Pseudolocalizer::Method::kAccent: @@ -313,12 +394,64 @@ ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base, default: break; } + modified.grammaticalInflection = grammaticalInflection; return modified; } +void GrammaticalGender(ResourceConfigValue* original_value, + ResourceConfigValue* localized_config_value, android::StringPool* pool, + ResourceEntry* entry, const Pseudolocalizer::Method method, + uint8_t grammaticalInflection) { + GrammaticalGenderVisitor visitor(pool, grammaticalInflection); + localized_config_value->value->Accept(&visitor); + + std::unique_ptr<Value> grammatical_gendered_value; + if (visitor.value) { + grammatical_gendered_value = std::move(visitor.value); + } else if (visitor.item) { + grammatical_gendered_value = std::move(visitor.item); + } + if (!grammatical_gendered_value) { + return; + } + + ConfigDescription config = + ModifyConfigForPseudoLocale(original_value->config, method, grammaticalInflection); + + ResourceConfigValue* grammatical_gendered_config_value = + entry->FindOrCreateValue(config, original_value->product); + if (!grammatical_gendered_config_value->value) { + // Only use auto-generated pseudo-localization if none is defined. + grammatical_gendered_config_value->value = std::move(grammatical_gendered_value); + } +} + +const uint32_t MASK_MASCULINE = 1; // Bit mask for masculine +const uint32_t MASK_FEMININE = 2; // Bit mask for feminine +const uint32_t MASK_NEUTER = 4; // Bit mask for neuter + +void GrammaticalGenderIfNeeded(ResourceConfigValue* original_value, ResourceConfigValue* new_value, + android::StringPool* pool, ResourceEntry* entry, + const Pseudolocalizer::Method method, uint32_t gender_state) { + if (gender_state & MASK_FEMININE) { + GrammaticalGender(original_value, new_value, pool, entry, method, + android::ResTable_config::GRAMMATICAL_GENDER_FEMININE); + } + + if (gender_state & MASK_MASCULINE) { + GrammaticalGender(original_value, new_value, pool, entry, method, + android::ResTable_config::GRAMMATICAL_GENDER_MASCULINE); + } + + if (gender_state & MASK_NEUTER) { + GrammaticalGender(original_value, new_value, pool, entry, method, + android::ResTable_config::GRAMMATICAL_GENDER_NEUTER); + } +} + void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method, ResourceConfigValue* original_value, android::StringPool* pool, - ResourceEntry* entry) { + ResourceEntry* entry, uint32_t gender_state, bool gender_flag) { Visitor visitor(pool, method); original_value->value->Accept(&visitor); @@ -333,8 +466,8 @@ void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method, return; } - ConfigDescription config_with_accent = - ModifyConfigForPseudoLocale(original_value->config, method); + ConfigDescription config_with_accent = ModifyConfigForPseudoLocale( + original_value->config, method, android::ResTable_config::GRAMMATICAL_GENDER_ANY); ResourceConfigValue* new_config_value = entry->FindOrCreateValue(config_with_accent, original_value->product); @@ -342,6 +475,9 @@ void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method, // Only use auto-generated pseudo-localization if none is defined. new_config_value->value = std::move(localized_value); } + if (gender_flag) { + GrammaticalGenderIfNeeded(original_value, new_config_value, pool, entry, method, gender_state); + } } // A value is pseudolocalizable if it does not define a locale (or is the default locale) and is @@ -356,16 +492,71 @@ static bool IsPseudolocalizable(ResourceConfigValue* config_value) { } // namespace +bool ParseGenderValuesAndSaveState(const std::string& grammatical_gender_values, + uint32_t* gender_state, android::IDiagnostics* diag) { + std::vector<std::string> values = util::SplitAndLowercase(grammatical_gender_values, ','); + for (size_t i = 0; i < values.size(); i++) { + if (values[i].length() != 0) { + if (values[i] == "f") { + *gender_state |= MASK_FEMININE; + } else if (values[i] == "m") { + *gender_state |= MASK_MASCULINE; + } else if (values[i] == "n") { + *gender_state |= MASK_NEUTER; + } else { + diag->Error(android::DiagMessage() << "Invalid grammatical gender value: " << values[i]); + return false; + } + } + } + return true; +} + +bool ParseGenderRatio(const std::string& grammatical_gender_ratio, float* gender_ratio, + android::IDiagnostics* diag) { + const char* input = grammatical_gender_ratio.c_str(); + char* endPtr; + errno = 0; + *gender_ratio = strtof(input, &endPtr); + if (endPtr == input || *endPtr != '\0' || errno == ERANGE || *gender_ratio < 0 || + *gender_ratio > 1) { + diag->Error(android::DiagMessage() + << "Invalid grammatical gender ratio: " << grammatical_gender_ratio + << ", must be a real number between 0 and 1"); + return false; + } + return true; +} + bool PseudolocaleGenerator::Consume(IAaptContext* context, ResourceTable* table) { + uint32_t gender_state = 0; + if (!ParseGenderValuesAndSaveState(grammatical_gender_values_, &gender_state, + context->GetDiagnostics())) { + return false; + } + + float gender_ratio = 0; + if (!ParseGenderRatio(grammatical_gender_ratio_, &gender_ratio, context->GetDiagnostics())) { + return false; + } + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> distrib(0.0, 1.0); + for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { + bool gender_flag = false; + if (distrib(gen) < gender_ratio) { + gender_flag = true; + } std::vector<ResourceConfigValue*> values = entry->FindValuesIf(IsPseudolocalizable); for (ResourceConfigValue* value : values) { PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, &table->string_pool, - entry.get()); + entry.get(), gender_state, gender_flag); PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, &table->string_pool, - entry.get()); + entry.get(), gender_state, gender_flag); } } } diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h index 44e6e3e86f92..ce92008cdba1 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.h +++ b/tools/aapt2/compile/PseudolocaleGenerator.h @@ -27,8 +27,19 @@ std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, Pseudolocalizer::Method method, android::StringPool* pool); -struct PseudolocaleGenerator : public IResourceTableConsumer { - bool Consume(IAaptContext* context, ResourceTable* table) override; +class PseudolocaleGenerator : public IResourceTableConsumer { + public: + explicit PseudolocaleGenerator(std::string grammatical_gender_values, + std::string grammatical_gender_ratio) + : grammatical_gender_values_(std::move(grammatical_gender_values)), + grammatical_gender_ratio_(std::move(grammatical_gender_ratio)) { + } + + bool Consume(IAaptContext* context, ResourceTable* table); + + private: + std::string grammatical_gender_values_; + std::string grammatical_gender_ratio_; }; } // namespace aapt diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp index 2f90cbf722c2..1477ebf8473d 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -197,7 +197,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) { val->SetTranslatable(false); std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - PseudolocaleGenerator generator; + PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0")); ASSERT_TRUE(generator.Consume(context.get(), table.get())); // Normal pseudolocalization should take place. @@ -249,7 +249,7 @@ TEST(PseudolocaleGeneratorTest, PluralsArePseudolocalized) { expected->values = {util::make_unique<String>(table->string_pool.MakeRef("[žéŕö one]")), util::make_unique<String>(table->string_pool.MakeRef("[öñé one]"))}; - PseudolocaleGenerator generator; + PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0")); ASSERT_TRUE(generator.Consume(context.get(), table.get())); const auto* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", @@ -287,7 +287,7 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) { context->GetDiagnostics())); } - PseudolocaleGenerator generator; + PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0")); ASSERT_TRUE(generator.Consume(context.get(), table.get())); StyledString* new_styled_string = test::GetValueForConfig<StyledString>( @@ -305,4 +305,213 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) { EXPECT_NE(std::string::npos, new_string->value->find("world")); } +TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForString) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0")); + ASSERT_TRUE(generator.Consume(context.get(), table.get())); + + String* locale = test::GetValueForConfig<String>(table.get(), "android:string/foo", + test::ParseConfigOrDie("en-rXA")); + ASSERT_NE(nullptr, locale); + + // Grammatical gendered string + auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine"); + config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + String* feminine = + test::GetValueForConfig<String>(table.get(), "android:string/foo", config_feminine); + ASSERT_NE(nullptr, feminine); + EXPECT_EQ(std::string("(F)") + *locale->value, *feminine->value); + + auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine"); + config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + String* masculine = + test::GetValueForConfig<String>(table.get(), "android:string/foo", config_masculine); + ASSERT_NE(nullptr, masculine); + EXPECT_EQ(std::string("(M)") + *locale->value, *masculine->value); + + auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter"); + config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + String* neuter = + test::GetValueForConfig<String>(table.get(), "android:string/foo", config_neuter); + ASSERT_NE(nullptr, neuter); + EXPECT_EQ(std::string("(N)") + *locale->value, *neuter->value); +} + +TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForPlural) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build(); + std::unique_ptr<Plural> plural = util::make_unique<Plural>(); + plural->values = {util::make_unique<String>(table->string_pool.MakeRef("zero")), + util::make_unique<String>(table->string_pool.MakeRef("one"))}; + ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.pkg:plurals/foo")) + .SetValue(std::move(plural)) + .Build(), + context->GetDiagnostics())); + PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0")); + ASSERT_TRUE(generator.Consume(context.get(), table.get())); + + Plural* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", + test::ParseConfigOrDie("en-rXA")); + ASSERT_NE(nullptr, actual); + + // Grammatical gendered Plural + auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine"); + config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + Plural* actual_feminine = + test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_feminine); + for (size_t i = 0; i < actual->values.size(); i++) { + if (actual->values[i]) { + String* locale = ValueCast<String>(actual->values[i].get()); + String* feminine = ValueCast<String>(actual_feminine->values[i].get()); + EXPECT_EQ(std::string("(F)") + *locale->value, *feminine->value); + } + } + + auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine"); + config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + Plural* actual_masculine = + test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_masculine); + ASSERT_NE(nullptr, actual_masculine); + for (size_t i = 0; i < actual->values.size(); i++) { + if (actual->values[i]) { + String* locale = ValueCast<String>(actual->values[i].get()); + String* masculine = ValueCast<String>(actual_masculine->values[i].get()); + EXPECT_EQ(std::string("(M)") + *locale->value, *masculine->value); + } + } + + auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter"); + config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + Plural* actual_neuter = + test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_neuter); + for (size_t i = 0; i < actual->values.size(); i++) { + if (actual->values[i]) { + String* locale = ValueCast<String>(actual->values[i].get()); + String* neuter = ValueCast<String>(actual_neuter->values[i].get()); + EXPECT_EQ(std::string("(N)") + *locale->value, *neuter->value); + } + } +} + +TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForStyledString) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build(); + android::StyleString original_style; + original_style.str = "Hello world!"; + original_style.spans = {android::Span{"i", 1, 10}}; + + std::unique_ptr<StyledString> original = + util::make_unique<StyledString>(table->string_pool.MakeRef(original_style)); + ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/foo")) + .SetValue(std::move(original)) + .Build(), + context->GetDiagnostics())); + PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0")); + ASSERT_TRUE(generator.Consume(context.get(), table.get())); + + StyledString* locale = test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", + test::ParseConfigOrDie("en-rXA")); + ASSERT_NE(nullptr, locale); + EXPECT_EQ(1, locale->value->spans.size()); + EXPECT_EQ(std::string("i"), *locale->value->spans[0].name); + + // Grammatical gendered StyledString + auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine"); + config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + StyledString* feminine = + test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_feminine); + ASSERT_NE(nullptr, feminine); + EXPECT_EQ(1, feminine->value->spans.size()); + EXPECT_EQ(std::string("i"), *feminine->value->spans[0].name); + EXPECT_EQ(std::string("(F)") + locale->value->value, feminine->value->value); + + auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine"); + config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + StyledString* masculine = + test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_masculine); + ASSERT_NE(nullptr, masculine); + EXPECT_EQ(1, masculine->value->spans.size()); + EXPECT_EQ(std::string("i"), *masculine->value->spans[0].name); + EXPECT_EQ(std::string("(M)") + locale->value->value, masculine->value->value); + + auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter"); + config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + StyledString* neuter = + test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_neuter); + ASSERT_NE(nullptr, neuter); + EXPECT_EQ(1, neuter->value->spans.size()); + EXPECT_EQ(std::string("i"), *neuter->value->spans[0].name); + EXPECT_EQ(std::string("(N)") + locale->value->value, neuter->value->value); +} + +TEST(PseudolocaleGeneratorTest, GrammaticalGenderForCertainValues) { + // single gender value + std::unique_ptr<ResourceTable> table_0 = + test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build(); + + std::unique_ptr<IAaptContext> context_0 = test::ContextBuilder().Build(); + PseudolocaleGenerator generator_0(std::string("f"), std::string("1.0")); + ASSERT_TRUE(generator_0.Consume(context_0.get(), table_0.get())); + + String* locale_0 = test::GetValueForConfig<String>(table_0.get(), "android:string/foo", + test::ParseConfigOrDie("en-rXA")); + ASSERT_NE(nullptr, locale_0); + + auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine"); + config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + String* feminine_0 = + test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_feminine); + ASSERT_NE(nullptr, feminine_0); + EXPECT_EQ(std::string("(F)") + *locale_0->value, *feminine_0->value); + + auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine"); + config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + String* masculine_0 = + test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_masculine); + EXPECT_EQ(nullptr, masculine_0); + + auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter"); + config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY; + String* neuter_0 = + test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_neuter); + EXPECT_EQ(nullptr, neuter_0); + + // multiple gender values + std::unique_ptr<ResourceTable> table_1 = + test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build(); + + std::unique_ptr<IAaptContext> context_1 = test::ContextBuilder().Build(); + PseudolocaleGenerator generator_1(std::string("f,n"), std::string("1.0")); + ASSERT_TRUE(generator_1.Consume(context_1.get(), table_1.get())); + + String* locale_1 = test::GetValueForConfig<String>(table_1.get(), "android:string/foo", + test::ParseConfigOrDie("en-rXA")); + ASSERT_NE(nullptr, locale_1); + + String* feminine_1 = + test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_feminine); + ASSERT_NE(nullptr, feminine_1); + EXPECT_EQ(std::string("(F)") + *locale_1->value, *feminine_1->value); + + String* masculine_1 = + test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_masculine); + EXPECT_EQ(nullptr, masculine_1); + + String* neuter_1 = + test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_neuter); + ASSERT_NE(nullptr, neuter_1); + EXPECT_EQ(std::string("(N)") + *locale_1->value, *neuter_1->value); + + // invalid gender value + std::unique_ptr<ResourceTable> table_2 = + test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build(); + + std::unique_ptr<IAaptContext> context_2 = test::ContextBuilder().Build(); + PseudolocaleGenerator generator_2(std::string("invald,"), std::string("1.0")); + ASSERT_FALSE(generator_2.Consume(context_2.get(), table_2.get())); +} + } // namespace aapt |