PseudolocaleGenerator.cpp revision 393b5f0d6130d3848dd82075986a5cf40c09ce44
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "ResourceTable.h" 18#include "ResourceValues.h" 19#include "ValueVisitor.h" 20#include "compile/PseudolocaleGenerator.h" 21#include "compile/Pseudolocalizer.h" 22#include "util/Comparators.h" 23 24namespace aapt { 25 26std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string, 27 Pseudolocalizer::Method method, 28 StringPool* pool) { 29 Pseudolocalizer localizer(method); 30 31 const StringPiece16 originalText = *string->value->str; 32 33 StyleString localized; 34 35 // Copy the spans. We will update their offsets when we localize. 36 localized.spans.reserve(string->value->spans.size()); 37 for (const StringPool::Span& span : string->value->spans) { 38 localized.spans.push_back(Span{ *span.name, span.firstChar, span.lastChar }); 39 } 40 41 // The ranges are all represented with a single value. This is the start of one range and 42 // end of another. 43 struct Range { 44 size_t start; 45 46 // Once the new string is localized, these are the pointers to the spans to adjust. 47 // Since this struct represents the start of one range and end of another, we have 48 // the two pointers respectively. 49 uint32_t* updateStart; 50 uint32_t* updateEnd; 51 }; 52 53 auto cmp = [](const Range& r, size_t index) -> bool { 54 return r.start < index; 55 }; 56 57 // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7] 58 // The ranges are the spaces in between. In this example, with a total string length of 9, 59 // the vector represents: (0,1], (2,4], (5,6], (7,9] 60 // 61 std::vector<Range> ranges; 62 ranges.push_back(Range{ 0 }); 63 ranges.push_back(Range{ originalText.size() - 1 }); 64 for (size_t i = 0; i < string->value->spans.size(); i++) { 65 const StringPool::Span& span = string->value->spans[i]; 66 67 // Insert or update the Range marker for the start of this span. 68 auto iter = std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp); 69 if (iter != ranges.end() && iter->start == span.firstChar) { 70 iter->updateStart = &localized.spans[i].firstChar; 71 } else { 72 ranges.insert(iter, 73 Range{ span.firstChar, &localized.spans[i].firstChar, nullptr }); 74 } 75 76 // Insert or update the Range marker for the end of this span. 77 iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp); 78 if (iter != ranges.end() && iter->start == span.lastChar) { 79 iter->updateEnd = &localized.spans[i].lastChar; 80 } else { 81 ranges.insert(iter, 82 Range{ span.lastChar, nullptr, &localized.spans[i].lastChar }); 83 } 84 } 85 86 localized.str += localizer.start(); 87 88 // Iterate over the ranges and localize each section. 89 for (size_t i = 0; i < ranges.size(); i++) { 90 const size_t start = ranges[i].start; 91 size_t len = originalText.size() - start; 92 if (i + 1 < ranges.size()) { 93 len = ranges[i + 1].start - start; 94 } 95 96 if (ranges[i].updateStart) { 97 *ranges[i].updateStart = localized.str.size(); 98 } 99 100 if (ranges[i].updateEnd) { 101 *ranges[i].updateEnd = localized.str.size(); 102 } 103 104 localized.str += localizer.text(originalText.substr(start, len)); 105 } 106 107 localized.str += localizer.end(); 108 109 std::unique_ptr<StyledString> localizedString = util::make_unique<StyledString>( 110 pool->makeRef(localized)); 111 localizedString->setSource(string->getSource()); 112 return localizedString; 113} 114 115namespace { 116 117struct Visitor : public RawValueVisitor { 118 StringPool* mPool; 119 Pseudolocalizer::Method mMethod; 120 Pseudolocalizer mLocalizer; 121 122 // Either value or item will be populated upon visiting the value. 123 std::unique_ptr<Value> mValue; 124 std::unique_ptr<Item> mItem; 125 126 Visitor(StringPool* pool, Pseudolocalizer::Method method) : 127 mPool(pool), mMethod(method), mLocalizer(method) { 128 } 129 130 void visit(Array* array) override { 131 std::unique_ptr<Array> localized = util::make_unique<Array>(); 132 localized->items.resize(array->items.size()); 133 for (size_t i = 0; i < array->items.size(); i++) { 134 Visitor subVisitor(mPool, mMethod); 135 array->items[i]->accept(&subVisitor); 136 if (subVisitor.mItem) { 137 localized->items[i] = std::move(subVisitor.mItem); 138 } else { 139 localized->items[i] = std::unique_ptr<Item>(array->items[i]->clone(mPool)); 140 } 141 } 142 localized->setSource(array->getSource()); 143 localized->setWeak(true); 144 mValue = std::move(localized); 145 } 146 147 void visit(Plural* plural) override { 148 std::unique_ptr<Plural> localized = util::make_unique<Plural>(); 149 for (size_t i = 0; i < plural->values.size(); i++) { 150 Visitor subVisitor(mPool, mMethod); 151 if (plural->values[i]) { 152 plural->values[i]->accept(&subVisitor); 153 if (subVisitor.mValue) { 154 localized->values[i] = std::move(subVisitor.mItem); 155 } else { 156 localized->values[i] = std::unique_ptr<Item>(plural->values[i]->clone(mPool)); 157 } 158 } 159 } 160 localized->setSource(plural->getSource()); 161 localized->setWeak(true); 162 mValue = std::move(localized); 163 } 164 165 void visit(String* string) override { 166 if (!string->isTranslateable()) { 167 return; 168 } 169 170 std::u16string result = mLocalizer.start() + mLocalizer.text(*string->value) + 171 mLocalizer.end(); 172 std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result)); 173 localized->setSource(string->getSource()); 174 localized->setWeak(true); 175 mItem = std::move(localized); 176 } 177 178 void visit(StyledString* string) override { 179 if (!string->isTranslateable()) { 180 return; 181 } 182 183 mItem = pseudolocalizeStyledString(string, mMethod, mPool); 184 mItem->setWeak(true); 185 } 186}; 187 188ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base, 189 Pseudolocalizer::Method m) { 190 ConfigDescription modified = base; 191 switch (m) { 192 case Pseudolocalizer::Method::kAccent: 193 modified.language[0] = 'e'; 194 modified.language[1] = 'n'; 195 modified.country[0] = 'X'; 196 modified.country[1] = 'A'; 197 break; 198 199 case Pseudolocalizer::Method::kBidi: 200 modified.language[0] = 'a'; 201 modified.language[1] = 'r'; 202 modified.country[0] = 'X'; 203 modified.country[1] = 'B'; 204 break; 205 default: 206 break; 207 } 208 return modified; 209} 210 211void pseudolocalizeIfNeeded(std::vector<ResourceConfigValue>* configValues, 212 Pseudolocalizer::Method method, StringPool* pool, Value* value) { 213 Visitor visitor(pool, method); 214 value->accept(&visitor); 215 216 std::unique_ptr<Value> localizedValue; 217 if (visitor.mValue) { 218 localizedValue = std::move(visitor.mValue); 219 } else if (visitor.mItem) { 220 localizedValue = std::move(visitor.mItem); 221 } 222 223 if (localizedValue) { 224 ConfigDescription pseudolocalizedConfig = modifyConfigForPseudoLocale(ConfigDescription{}, 225 method); 226 auto iter = std::lower_bound(configValues->begin(), configValues->end(), 227 pseudolocalizedConfig, cmp::lessThanConfig); 228 if (iter == configValues->end() || iter->config != pseudolocalizedConfig) { 229 // The pseudolocalized config doesn't exist, add it. 230 configValues->insert(iter, ResourceConfigValue{ pseudolocalizedConfig, 231 std::move(localizedValue) }); 232 } 233 } 234} 235 236} // namespace 237 238bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) { 239 for (auto& package : table->packages) { 240 for (auto& type : package->types) { 241 for (auto& entry : type->entries) { 242 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), 243 ConfigDescription{}, cmp::lessThanConfig); 244 if (iter != entry->values.end() && iter->config == ConfigDescription{}) { 245 // Only pseudolocalize the default configuration. 246 247 // The iterator will be invalidated, so grab a pointer to the value. 248 Value* originalValue = iter->value.get(); 249 250 pseudolocalizeIfNeeded(&entry->values, Pseudolocalizer::Method::kAccent, 251 &table->stringPool, originalValue); 252 pseudolocalizeIfNeeded(&entry->values, Pseudolocalizer::Method::kBidi, 253 &table->stringPool, originalValue); 254 } 255 } 256 } 257 } 258 return true; 259} 260 261} // namespace aapt 262