| /* |
| * 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 "ResourceTable.h" |
| #include "ResourceValues.h" |
| #include "ValueVisitor.h" |
| #include "compile/PseudolocaleGenerator.h" |
| #include "compile/Pseudolocalizer.h" |
| |
| #include <algorithm> |
| |
| namespace aapt { |
| |
| std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string, |
| Pseudolocalizer::Method method, |
| StringPool* pool) { |
| Pseudolocalizer localizer(method); |
| |
| const StringPiece16 originalText = *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.firstChar, span.lastChar }); |
| } |
| |
| // 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* updateStart; |
| uint32_t* updateEnd; |
| }; |
| |
| 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<Range> ranges; |
| ranges.push_back(Range{ 0 }); |
| ranges.push_back(Range{ originalText.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.firstChar, cmp); |
| if (iter != ranges.end() && iter->start == span.firstChar) { |
| iter->updateStart = &localized.spans[i].firstChar; |
| } else { |
| ranges.insert(iter, |
| Range{ span.firstChar, &localized.spans[i].firstChar, nullptr }); |
| } |
| |
| // Insert or update the Range marker for the end of this span. |
| iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp); |
| if (iter != ranges.end() && iter->start == span.lastChar) { |
| iter->updateEnd = &localized.spans[i].lastChar; |
| } else { |
| ranges.insert(iter, |
| Range{ span.lastChar, nullptr, &localized.spans[i].lastChar }); |
| } |
| } |
| |
| 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 = originalText.size() - start; |
| if (i + 1 < ranges.size()) { |
| len = ranges[i + 1].start - start; |
| } |
| |
| if (ranges[i].updateStart) { |
| *ranges[i].updateStart = localized.str.size(); |
| } |
| |
| if (ranges[i].updateEnd) { |
| *ranges[i].updateEnd = localized.str.size(); |
| } |
| |
| localized.str += localizer.text(originalText.substr(start, len)); |
| } |
| |
| localized.str += localizer.end(); |
| |
| std::unique_ptr<StyledString> localizedString = util::make_unique<StyledString>( |
| pool->makeRef(localized)); |
| localizedString->setSource(string->getSource()); |
| return localizedString; |
| } |
| |
| namespace { |
| |
| struct Visitor : public RawValueVisitor { |
| StringPool* mPool; |
| Pseudolocalizer::Method mMethod; |
| Pseudolocalizer mLocalizer; |
| |
| // Either value or item will be populated upon visiting the value. |
| std::unique_ptr<Value> mValue; |
| std::unique_ptr<Item> mItem; |
| |
| Visitor(StringPool* pool, Pseudolocalizer::Method method) : |
| mPool(pool), mMethod(method), mLocalizer(method) { |
| } |
| |
| void visit(Array* array) override { |
| std::unique_ptr<Array> localized = util::make_unique<Array>(); |
| localized->items.resize(array->items.size()); |
| for (size_t i = 0; i < array->items.size(); i++) { |
| Visitor subVisitor(mPool, mMethod); |
| array->items[i]->accept(&subVisitor); |
| if (subVisitor.mItem) { |
| localized->items[i] = std::move(subVisitor.mItem); |
| } else { |
| localized->items[i] = std::unique_ptr<Item>(array->items[i]->clone(mPool)); |
| } |
| } |
| localized->setSource(array->getSource()); |
| localized->setWeak(true); |
| mValue = std::move(localized); |
| } |
| |
| void visit(Plural* plural) override { |
| std::unique_ptr<Plural> localized = util::make_unique<Plural>(); |
| for (size_t i = 0; i < plural->values.size(); i++) { |
| Visitor subVisitor(mPool, mMethod); |
| if (plural->values[i]) { |
| plural->values[i]->accept(&subVisitor); |
| if (subVisitor.mValue) { |
| localized->values[i] = std::move(subVisitor.mItem); |
| } else { |
| localized->values[i] = std::unique_ptr<Item>(plural->values[i]->clone(mPool)); |
| } |
| } |
| } |
| localized->setSource(plural->getSource()); |
| localized->setWeak(true); |
| mValue = std::move(localized); |
| } |
| |
| void visit(String* string) override { |
| if (!string->isTranslateable()) { |
| return; |
| } |
| |
| std::u16string result = mLocalizer.start() + mLocalizer.text(*string->value) + |
| mLocalizer.end(); |
| std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result)); |
| localized->setSource(string->getSource()); |
| localized->setWeak(true); |
| mItem = std::move(localized); |
| } |
| |
| void visit(StyledString* string) override { |
| if (!string->isTranslateable()) { |
| return; |
| } |
| |
| mItem = pseudolocalizeStyledString(string, mMethod, mPool); |
| mItem->setWeak(true); |
| } |
| }; |
| |
| 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* originalValue, |
| StringPool* pool, |
| ResourceEntry* entry) { |
| Visitor visitor(pool, method); |
| originalValue->value->accept(&visitor); |
| |
| std::unique_ptr<Value> localizedValue; |
| if (visitor.mValue) { |
| localizedValue = std::move(visitor.mValue); |
| } else if (visitor.mItem) { |
| localizedValue = std::move(visitor.mItem); |
| } |
| |
| if (!localizedValue) { |
| return; |
| } |
| |
| ConfigDescription configWithAccent = modifyConfigForPseudoLocale( |
| originalValue->config, method); |
| |
| ResourceConfigValue* newConfigValue = entry->findOrCreateValue( |
| configWithAccent, originalValue->product); |
| if (!newConfigValue->value) { |
| // Only use auto-generated pseudo-localization if none is defined. |
| newConfigValue->value = std::move(localizedValue); |
| } |
| } |
| |
| } // 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<ResourceConfigValue*> values = entry->findAllValues( |
| ConfigDescription::defaultConfig()); |
| for (ResourceConfigValue* value : values) { |
| pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, |
| &table->stringPool, entry.get()); |
| pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, |
| &table->stringPool, entry.get()); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| } // namespace aapt |