Pseudolocale support for grammatical gender
Default behavior is generate grammatical gender strings with different
prefix when pseudolocale is enabled. Also extend two specifiers to
support generate grammatical gender string for a specific gender and for
a spefific ratio. go/pseudolocale-support-for-grammatical-gender
Bug: b/272626712
Test: Added and verified affected atests pass
Change-Id: I6b7514425898facb68f0b1f6fb09e4f87978c03d
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index d2ea599..b5c290e 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -186,7 +186,20 @@
// 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 14a730a..22890fc 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -35,6 +35,8 @@
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 @@
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 3464a76..8880089 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -236,9 +236,24 @@
// 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 09a8560..8143052 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 @@
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 @@
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 @@
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 @@
// 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 @@
} // 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 44e6e3e..ce92008 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.h
+++ b/tools/aapt2/compile/PseudolocaleGenerator.h
@@ -27,8 +27,19 @@
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 2f90cbf..1477ebf 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -197,7 +197,7 @@
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 @@
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 @@
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 @@
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