summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Brandon Liu <branliu@google.com> 2023-05-25 22:41:10 +0000
committer Brandon Liu <branliu@google.com> 2023-06-28 18:40:49 +0000
commit5274e069cd6b861e6fc0d4e65b35dbcf7974be5c (patch)
tree6a729a5e53e9fe8e57bbe15f40dfe1b693c00bdc
parent6c07cbf0988666401707d89a017d379f4a4ffc2a (diff)
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
-rw-r--r--tools/aapt2/cmd/Compile.cpp15
-rw-r--r--tools/aapt2/cmd/Compile.h11
-rw-r--r--tools/aapt2/cmd/Compile_test.cpp21
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator.cpp203
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator.h15
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator_test.cpp215
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