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