Merge "Pseudolocale support for grammatical gender" into main
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user