blob: fad9edd04e4c4e7c7607461819eb85b30c4af01c [file] [log] [blame]
Adam Lesinski393b5f02015-12-17 13:03:11 -08001/*
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
Adam Lesinskicacb28f2016-10-19 12:18:14 -070017#include "compile/PseudolocaleGenerator.h"
Adam Lesinskice5e56e2016-10-21 17:56:45 -070018
19#include <algorithm>
20
Adam Lesinski393b5f02015-12-17 13:03:11 -080021#include "ResourceTable.h"
22#include "ResourceValues.h"
23#include "ValueVisitor.h"
Adam Lesinski393b5f02015-12-17 13:03:11 -080024#include "compile/Pseudolocalizer.h"
Adam Lesinski393b5f02015-12-17 13:03:11 -080025
Adam Lesinskid5083f62017-01-16 15:07:21 -080026using android::StringPiece;
27
Adam Lesinski393b5f02015-12-17 13:03:11 -080028namespace aapt {
29
Adam Lesinskice5e56e2016-10-21 17:56:45 -070030std::unique_ptr<StyledString> PseudolocalizeStyledString(
Adam Lesinskicacb28f2016-10-19 12:18:14 -070031 StyledString* string, Pseudolocalizer::Method method, StringPool* pool) {
32 Pseudolocalizer localizer(method);
Adam Lesinski393b5f02015-12-17 13:03:11 -080033
Adam Lesinskice5e56e2016-10-21 17:56:45 -070034 const StringPiece original_text = *string->value->str;
Adam Lesinski393b5f02015-12-17 13:03:11 -080035
Adam Lesinskicacb28f2016-10-19 12:18:14 -070036 StyleString localized;
Adam Lesinski393b5f02015-12-17 13:03:11 -080037
Adam Lesinskicacb28f2016-10-19 12:18:14 -070038 // Copy the spans. We will update their offsets when we localize.
39 localized.spans.reserve(string->value->spans.size());
40 for (const StringPool::Span& span : string->value->spans) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070041 localized.spans.push_back(
42 Span{*span.name, span.first_char, span.last_char});
Adam Lesinskicacb28f2016-10-19 12:18:14 -070043 }
44
45 // The ranges are all represented with a single value. This is the start of
Adam Lesinski75421622017-01-06 15:20:04 -080046 // one range and end of another.
Adam Lesinskicacb28f2016-10-19 12:18:14 -070047 struct Range {
48 size_t start;
49
Adam Lesinski75421622017-01-06 15:20:04 -080050 // If set to true, toggles the state of translatability.
51 bool toggle_translatability;
52
53 // Once the new string is localized, these are the pointers to the spans to adjust.
Adam Lesinskicacb28f2016-10-19 12:18:14 -070054 // Since this struct represents the start of one range and end of another,
Adam Lesinski75421622017-01-06 15:20:04 -080055 // we have the two pointers respectively.
Adam Lesinskice5e56e2016-10-21 17:56:45 -070056 uint32_t* update_start;
57 uint32_t* update_end;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070058 };
59
60 auto cmp = [](const Range& r, size_t index) -> bool {
61 return r.start < index;
62 };
63
64 // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7]
65 // The ranges are the spaces in between. In this example, with a total string
Adam Lesinski75421622017-01-06 15:20:04 -080066 // length of 9, the vector represents: (0,1], (2,4], (5,6], (7,9]
Adam Lesinskicacb28f2016-10-19 12:18:14 -070067 //
68 std::vector<Range> ranges;
Adam Lesinski75421622017-01-06 15:20:04 -080069 ranges.push_back(Range{0, false, nullptr, nullptr});
70 ranges.push_back(Range{original_text.size() - 1, false, nullptr, nullptr});
Adam Lesinskicacb28f2016-10-19 12:18:14 -070071 for (size_t i = 0; i < string->value->spans.size(); i++) {
72 const StringPool::Span& span = string->value->spans[i];
73
74 // Insert or update the Range marker for the start of this span.
75 auto iter =
Adam Lesinskice5e56e2016-10-21 17:56:45 -070076 std::lower_bound(ranges.begin(), ranges.end(), span.first_char, cmp);
77 if (iter != ranges.end() && iter->start == span.first_char) {
78 iter->update_start = &localized.spans[i].first_char;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070079 } else {
Adam Lesinski75421622017-01-06 15:20:04 -080080 ranges.insert(iter, Range{span.first_char, false, &localized.spans[i].first_char, nullptr});
Adam Lesinski393b5f02015-12-17 13:03:11 -080081 }
82
Adam Lesinskicacb28f2016-10-19 12:18:14 -070083 // Insert or update the Range marker for the end of this span.
Adam Lesinskice5e56e2016-10-21 17:56:45 -070084 iter = std::lower_bound(ranges.begin(), ranges.end(), span.last_char, cmp);
85 if (iter != ranges.end() && iter->start == span.last_char) {
86 iter->update_end = &localized.spans[i].last_char;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070087 } else {
Adam Lesinski75421622017-01-06 15:20:04 -080088 ranges.insert(iter, Range{span.last_char, false, nullptr, &localized.spans[i].last_char});
89 }
90 }
91
92 // Parts of the string may be untranslatable. Merge those ranges
93 // in as well, so that we have continuous sections of text to
94 // feed into the pseudolocalizer.
95 // We do this by marking the beginning of a range as either toggling
96 // the translatability state or not.
97 for (const UntranslatableSection& section : string->untranslatable_sections) {
98 auto iter = std::lower_bound(ranges.begin(), ranges.end(), section.start, cmp);
99 if (iter != ranges.end() && iter->start == section.start) {
100 // An existing span starts (or ends) here. We just need to mark that
101 // the translatability should toggle here. If translatability was
102 // already being toggled, then that means we have two adjacent ranges of untranslatable
103 // text, so remove the toggle and only toggle at the end of this range,
104 // effectively merging these ranges.
105 iter->toggle_translatability = !iter->toggle_translatability;
106 } else {
107 // Insert a new range that specifies to toggle the translatability.
108 iter = ranges.insert(iter, Range{section.start, true, nullptr, nullptr});
109 }
110
111 // Update/create an end to the untranslatable section.
112 iter = std::lower_bound(iter, ranges.end(), section.end, cmp);
113 if (iter != ranges.end() && iter->start == section.end) {
114 iter->toggle_translatability = true;
115 } else {
116 iter = ranges.insert(iter, Range{section.end, true, nullptr, nullptr});
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700117 }
118 }
Adam Lesinski393b5f02015-12-17 13:03:11 -0800119
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700120 localized.str += localizer.Start();
Adam Lesinski393b5f02015-12-17 13:03:11 -0800121
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700122 // Iterate over the ranges and localize each section.
Adam Lesinski75421622017-01-06 15:20:04 -0800123 // The text starts as translatable, and each time a range has toggle_translatability
124 // set to true, we toggle whether to translate or not.
125 // This assumes no untranslatable ranges overlap.
126 bool translatable = true;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700127 for (size_t i = 0; i < ranges.size(); i++) {
128 const size_t start = ranges[i].start;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700129 size_t len = original_text.size() - start;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700130 if (i + 1 < ranges.size()) {
131 len = ranges[i + 1].start - start;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800132 }
133
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700134 if (ranges[i].update_start) {
135 *ranges[i].update_start = localized.str.size();
Adam Lesinski393b5f02015-12-17 13:03:11 -0800136 }
137
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700138 if (ranges[i].update_end) {
139 *ranges[i].update_end = localized.str.size();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700140 }
Adam Lesinski393b5f02015-12-17 13:03:11 -0800141
Adam Lesinski75421622017-01-06 15:20:04 -0800142 if (ranges[i].toggle_translatability) {
143 translatable = !translatable;
144 }
145
146 if (translatable) {
147 localized.str += localizer.Text(original_text.substr(start, len));
148 } else {
149 localized.str += original_text.substr(start, len);
150 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700151 }
152
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700153 localized.str += localizer.End();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700154
Adam Lesinski75421622017-01-06 15:20:04 -0800155 return util::make_unique<StyledString>(pool->MakeRef(localized));
Adam Lesinski393b5f02015-12-17 13:03:11 -0800156}
157
158namespace {
159
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700160class Visitor : public RawValueVisitor {
161 public:
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700162 // Either value or item will be populated upon visiting the value.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700163 std::unique_ptr<Value> value;
164 std::unique_ptr<Item> item;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800165
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700166 Visitor(StringPool* pool, Pseudolocalizer::Method method)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700167 : pool_(pool), method_(method), localizer_(method) {}
Adam Lesinski393b5f02015-12-17 13:03:11 -0800168
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700169 void Visit(Plural* plural) override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700170 std::unique_ptr<Plural> localized = util::make_unique<Plural>();
171 for (size_t i = 0; i < plural->values.size(); i++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700172 Visitor sub_visitor(pool_, method_);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700173 if (plural->values[i]) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700174 plural->values[i]->Accept(&sub_visitor);
175 if (sub_visitor.value) {
176 localized->values[i] = std::move(sub_visitor.item);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700177 } else {
178 localized->values[i] =
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700179 std::unique_ptr<Item>(plural->values[i]->Clone(pool_));
Adam Lesinski393b5f02015-12-17 13:03:11 -0800180 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700181 }
Adam Lesinski393b5f02015-12-17 13:03:11 -0800182 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700183 localized->SetSource(plural->GetSource());
184 localized->SetWeak(true);
185 value = std::move(localized);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700186 }
Adam Lesinski393b5f02015-12-17 13:03:11 -0800187
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700188 void Visit(String* string) override {
Adam Lesinski75421622017-01-06 15:20:04 -0800189 const StringPiece original_string = *string->value;
190 std::string result = localizer_.Start();
191
192 // Pseudolocalize only the translatable sections.
193 size_t start = 0u;
194 for (const UntranslatableSection& section : string->untranslatable_sections) {
195 // Pseudolocalize the content before the untranslatable section.
196 const size_t len = section.start - start;
197 if (len > 0u) {
198 result += localizer_.Text(original_string.substr(start, len));
199 }
200
201 // Copy the untranslatable content.
202 result += original_string.substr(section.start, section.end - section.start);
203 start = section.end;
204 }
205
206 // Pseudolocalize the content after the last untranslatable section.
207 if (start != original_string.size()) {
208 const size_t len = original_string.size() - start;
209 result += localizer_.Text(original_string.substr(start, len));
210 }
211 result += localizer_.End();
212
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700213 std::unique_ptr<String> localized =
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700214 util::make_unique<String>(pool_->MakeRef(result));
215 localized->SetSource(string->GetSource());
216 localized->SetWeak(true);
217 item = std::move(localized);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700218 }
Adam Lesinski393b5f02015-12-17 13:03:11 -0800219
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700220 void Visit(StyledString* string) override {
221 item = PseudolocalizeStyledString(string, method_, pool_);
Adam Lesinski75421622017-01-06 15:20:04 -0800222 item->SetSource(string->GetSource());
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700223 item->SetWeak(true);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700224 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700225
226 private:
227 DISALLOW_COPY_AND_ASSIGN(Visitor);
228
229 StringPool* pool_;
230 Pseudolocalizer::Method method_;
231 Pseudolocalizer localizer_;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800232};
233
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700234ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base,
Adam Lesinski393b5f02015-12-17 13:03:11 -0800235 Pseudolocalizer::Method m) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700236 ConfigDescription modified = base;
237 switch (m) {
Adam Lesinski393b5f02015-12-17 13:03:11 -0800238 case Pseudolocalizer::Method::kAccent:
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700239 modified.language[0] = 'e';
240 modified.language[1] = 'n';
241 modified.country[0] = 'X';
242 modified.country[1] = 'A';
243 break;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800244
245 case Pseudolocalizer::Method::kBidi:
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700246 modified.language[0] = 'a';
247 modified.language[1] = 'r';
248 modified.country[0] = 'X';
249 modified.country[1] = 'B';
250 break;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800251 default:
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700252 break;
253 }
254 return modified;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800255}
256
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700257void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
258 ResourceConfigValue* original_value,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700259 StringPool* pool, ResourceEntry* entry) {
260 Visitor visitor(pool, method);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700261 original_value->value->Accept(&visitor);
Adam Lesinski393b5f02015-12-17 13:03:11 -0800262
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700263 std::unique_ptr<Value> localized_value;
264 if (visitor.value) {
265 localized_value = std::move(visitor.value);
266 } else if (visitor.item) {
267 localized_value = std::move(visitor.item);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700268 }
Adam Lesinski393b5f02015-12-17 13:03:11 -0800269
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700270 if (!localized_value) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700271 return;
272 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800273
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700274 ConfigDescription config_with_accent =
275 ModifyConfigForPseudoLocale(original_value->config, method);
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800276
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700277 ResourceConfigValue* new_config_value =
278 entry->FindOrCreateValue(config_with_accent, original_value->product);
279 if (!new_config_value->value) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700280 // Only use auto-generated pseudo-localization if none is defined.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700281 new_config_value->value = std::move(localized_value);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700282 }
Adam Lesinski393b5f02015-12-17 13:03:11 -0800283}
284
Adam Lesinski458b8772016-04-25 14:20:21 -0700285/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700286 * A value is pseudolocalizable if it does not define a locale (or is the
287 * default locale)
Adam Lesinski75421622017-01-06 15:20:04 -0800288 * and is translatable.
Adam Lesinski458b8772016-04-25 14:20:21 -0700289 */
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700290static bool IsPseudolocalizable(ResourceConfigValue* config_value) {
291 const int diff =
292 config_value->config.diff(ConfigDescription::DefaultConfig());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700293 if (diff & ConfigDescription::CONFIG_LOCALE) {
294 return false;
295 }
Adam Lesinski75421622017-01-06 15:20:04 -0800296 return config_value->value->IsTranslatable();
Adam Lesinski458b8772016-04-25 14:20:21 -0700297}
298
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700299} // namespace
Adam Lesinski393b5f02015-12-17 13:03:11 -0800300
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700301bool PseudolocaleGenerator::Consume(IAaptContext* context,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700302 ResourceTable* table) {
303 for (auto& package : table->packages) {
304 for (auto& type : package->types) {
305 for (auto& entry : type->entries) {
306 std::vector<ResourceConfigValue*> values =
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700307 entry->FindValuesIf(IsPseudolocalizable);
Adam Lesinski458b8772016-04-25 14:20:21 -0700308
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700309 for (ResourceConfigValue* value : values) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700310 PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
311 &table->string_pool, entry.get());
312 PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
313 &table->string_pool, entry.get());
Adam Lesinski393b5f02015-12-17 13:03:11 -0800314 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700315 }
Adam Lesinski393b5f02015-12-17 13:03:11 -0800316 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700317 }
318 return true;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800319}
320
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700321} // namespace aapt