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
This commit is contained in:
Brandon Liu
2023-05-25 22:41:10 +00:00
parent 6c07cbf098
commit 5274e069cd
6 changed files with 465 additions and 15 deletions

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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