/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "compile/PseudolocaleGenerator.h" #include #include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" #include "compile/Pseudolocalizer.h" using android::StringPiece; namespace aapt { std::unique_ptr PseudolocalizeStyledString( StyledString* string, Pseudolocalizer::Method method, StringPool* pool) { Pseudolocalizer localizer(method); const StringPiece original_text = *string->value->str; StyleString localized; // Copy the spans. We will update their offsets when we localize. localized.spans.reserve(string->value->spans.size()); for (const StringPool::Span& span : string->value->spans) { localized.spans.push_back( Span{*span.name, span.first_char, span.last_char}); } // The ranges are all represented with a single value. This is the start of // one range and // end of another. struct Range { size_t start; // Once the new string is localized, these are the pointers to the spans to // adjust. // Since this struct represents the start of one range and end of another, // we have // the two pointers respectively. uint32_t* update_start; uint32_t* update_end; }; auto cmp = [](const Range& r, size_t index) -> bool { return r.start < index; }; // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7] // The ranges are the spaces in between. In this example, with a total string // length of 9, // the vector represents: (0,1], (2,4], (5,6], (7,9] // std::vector ranges; ranges.push_back(Range{0}); ranges.push_back(Range{original_text.size() - 1}); for (size_t i = 0; i < string->value->spans.size(); i++) { const StringPool::Span& span = string->value->spans[i]; // Insert or update the Range marker for the start of this span. auto iter = std::lower_bound(ranges.begin(), ranges.end(), span.first_char, cmp); if (iter != ranges.end() && iter->start == span.first_char) { iter->update_start = &localized.spans[i].first_char; } else { ranges.insert(iter, Range{span.first_char, &localized.spans[i].first_char, nullptr}); } // Insert or update the Range marker for the end of this span. iter = std::lower_bound(ranges.begin(), ranges.end(), span.last_char, cmp); if (iter != ranges.end() && iter->start == span.last_char) { iter->update_end = &localized.spans[i].last_char; } else { ranges.insert( iter, Range{span.last_char, nullptr, &localized.spans[i].last_char}); } } localized.str += localizer.Start(); // Iterate over the ranges and localize each section. for (size_t i = 0; i < ranges.size(); i++) { const size_t start = ranges[i].start; size_t len = original_text.size() - start; if (i + 1 < ranges.size()) { len = ranges[i + 1].start - start; } if (ranges[i].update_start) { *ranges[i].update_start = localized.str.size(); } if (ranges[i].update_end) { *ranges[i].update_end = localized.str.size(); } localized.str += localizer.Text(original_text.substr(start, len)); } localized.str += localizer.End(); std::unique_ptr localized_string = util::make_unique(pool->MakeRef(localized)); localized_string->SetSource(string->GetSource()); return localized_string; } namespace { class Visitor : public RawValueVisitor { public: // Either value or item will be populated upon visiting the value. std::unique_ptr value; std::unique_ptr item; Visitor(StringPool* pool, Pseudolocalizer::Method method) : pool_(pool), method_(method), localizer_(method) {} void Visit(Plural* plural) override { std::unique_ptr localized = util::make_unique(); for (size_t i = 0; i < plural->values.size(); i++) { Visitor sub_visitor(pool_, method_); if (plural->values[i]) { plural->values[i]->Accept(&sub_visitor); if (sub_visitor.value) { localized->values[i] = std::move(sub_visitor.item); } else { localized->values[i] = std::unique_ptr(plural->values[i]->Clone(pool_)); } } } localized->SetSource(plural->GetSource()); localized->SetWeak(true); value = std::move(localized); } void Visit(String* string) override { std::string result = localizer_.Start() + localizer_.Text(*string->value) + localizer_.End(); std::unique_ptr localized = util::make_unique(pool_->MakeRef(result)); localized->SetSource(string->GetSource()); localized->SetWeak(true); item = std::move(localized); } void Visit(StyledString* string) override { item = PseudolocalizeStyledString(string, method_, pool_); item->SetWeak(true); } private: DISALLOW_COPY_AND_ASSIGN(Visitor); StringPool* pool_; Pseudolocalizer::Method method_; Pseudolocalizer localizer_; }; ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base, Pseudolocalizer::Method m) { ConfigDescription modified = base; switch (m) { case Pseudolocalizer::Method::kAccent: modified.language[0] = 'e'; modified.language[1] = 'n'; modified.country[0] = 'X'; modified.country[1] = 'A'; break; case Pseudolocalizer::Method::kBidi: modified.language[0] = 'a'; modified.language[1] = 'r'; modified.country[0] = 'X'; modified.country[1] = 'B'; break; default: break; } return modified; } void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method, ResourceConfigValue* original_value, StringPool* pool, ResourceEntry* entry) { Visitor visitor(pool, method); original_value->value->Accept(&visitor); std::unique_ptr localized_value; if (visitor.value) { localized_value = std::move(visitor.value); } else if (visitor.item) { localized_value = std::move(visitor.item); } if (!localized_value) { return; } ConfigDescription config_with_accent = ModifyConfigForPseudoLocale(original_value->config, method); ResourceConfigValue* new_config_value = entry->FindOrCreateValue(config_with_accent, original_value->product); if (!new_config_value->value) { // Only use auto-generated pseudo-localization if none is defined. new_config_value->value = std::move(localized_value); } } /** * A value is pseudolocalizable if it does not define a locale (or is the * default locale) * and is translateable. */ static bool IsPseudolocalizable(ResourceConfigValue* config_value) { const int diff = config_value->config.diff(ConfigDescription::DefaultConfig()); if (diff & ConfigDescription::CONFIG_LOCALE) { return false; } return config_value->value->IsTranslateable(); } } // namespace bool PseudolocaleGenerator::Consume(IAaptContext* context, ResourceTable* table) { for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { std::vector values = entry->FindValuesIf(IsPseudolocalizable); for (ResourceConfigValue* value : values) { PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, &table->string_pool, entry.get()); PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, &table->string_pool, entry.get()); } } } } return true; } } // namespace aapt