PseudolocaleGenerator.cpp revision d5083f6f6b9bc76bbe64052bcec639eee752a321
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 "compile/PseudolocaleGenerator.h"
18
19#include <algorithm>
20
21#include "ResourceTable.h"
22#include "ResourceValues.h"
23#include "ValueVisitor.h"
24#include "compile/Pseudolocalizer.h"
25
26using android::StringPiece;
27
28namespace aapt {
29
30std::unique_ptr<StyledString> PseudolocalizeStyledString(
31    StyledString* string, Pseudolocalizer::Method method, StringPool* pool) {
32  Pseudolocalizer localizer(method);
33
34  const StringPiece original_text = *string->value->str;
35
36  StyleString localized;
37
38  // 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) {
41    localized.spans.push_back(
42        Span{*span.name, span.first_char, span.last_char});
43  }
44
45  // The ranges are all represented with a single value. This is the start of
46  // one range and
47  // end of another.
48  struct Range {
49    size_t start;
50
51    // Once the new string is localized, these are the pointers to the spans to
52    // adjust.
53    // Since this struct represents the start of one range and end of another,
54    // we have
55    // the two pointers respectively.
56    uint32_t* update_start;
57    uint32_t* update_end;
58  };
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
66  // length of 9,
67  // the vector represents: (0,1], (2,4], (5,6], (7,9]
68  //
69  std::vector<Range> ranges;
70  ranges.push_back(Range{0});
71  ranges.push_back(Range{original_text.size() - 1});
72  for (size_t i = 0; i < string->value->spans.size(); i++) {
73    const StringPool::Span& span = string->value->spans[i];
74
75    // Insert or update the Range marker for the start of this span.
76    auto iter =
77        std::lower_bound(ranges.begin(), ranges.end(), span.first_char, cmp);
78    if (iter != ranges.end() && iter->start == span.first_char) {
79      iter->update_start = &localized.spans[i].first_char;
80    } else {
81      ranges.insert(iter, Range{span.first_char, &localized.spans[i].first_char,
82                                nullptr});
83    }
84
85    // Insert or update the Range marker for the end of this span.
86    iter = std::lower_bound(ranges.begin(), ranges.end(), span.last_char, cmp);
87    if (iter != ranges.end() && iter->start == span.last_char) {
88      iter->update_end = &localized.spans[i].last_char;
89    } else {
90      ranges.insert(
91          iter, Range{span.last_char, nullptr, &localized.spans[i].last_char});
92    }
93  }
94
95  localized.str += localizer.Start();
96
97  // Iterate over the ranges and localize each section.
98  for (size_t i = 0; i < ranges.size(); i++) {
99    const size_t start = ranges[i].start;
100    size_t len = original_text.size() - start;
101    if (i + 1 < ranges.size()) {
102      len = ranges[i + 1].start - start;
103    }
104
105    if (ranges[i].update_start) {
106      *ranges[i].update_start = localized.str.size();
107    }
108
109    if (ranges[i].update_end) {
110      *ranges[i].update_end = localized.str.size();
111    }
112
113    localized.str += localizer.Text(original_text.substr(start, len));
114  }
115
116  localized.str += localizer.End();
117
118  std::unique_ptr<StyledString> localized_string =
119      util::make_unique<StyledString>(pool->MakeRef(localized));
120  localized_string->SetSource(string->GetSource());
121  return localized_string;
122}
123
124namespace {
125
126class Visitor : public RawValueVisitor {
127 public:
128  // Either value or item will be populated upon visiting the value.
129  std::unique_ptr<Value> value;
130  std::unique_ptr<Item> item;
131
132  Visitor(StringPool* pool, Pseudolocalizer::Method method)
133      : pool_(pool), method_(method), localizer_(method) {}
134
135  void Visit(Plural* plural) override {
136    std::unique_ptr<Plural> localized = util::make_unique<Plural>();
137    for (size_t i = 0; i < plural->values.size(); i++) {
138      Visitor sub_visitor(pool_, method_);
139      if (plural->values[i]) {
140        plural->values[i]->Accept(&sub_visitor);
141        if (sub_visitor.value) {
142          localized->values[i] = std::move(sub_visitor.item);
143        } else {
144          localized->values[i] =
145              std::unique_ptr<Item>(plural->values[i]->Clone(pool_));
146        }
147      }
148    }
149    localized->SetSource(plural->GetSource());
150    localized->SetWeak(true);
151    value = std::move(localized);
152  }
153
154  void Visit(String* string) override {
155    std::string result =
156        localizer_.Start() + localizer_.Text(*string->value) + localizer_.End();
157    std::unique_ptr<String> localized =
158        util::make_unique<String>(pool_->MakeRef(result));
159    localized->SetSource(string->GetSource());
160    localized->SetWeak(true);
161    item = std::move(localized);
162  }
163
164  void Visit(StyledString* string) override {
165    item = PseudolocalizeStyledString(string, method_, pool_);
166    item->SetWeak(true);
167  }
168
169 private:
170  DISALLOW_COPY_AND_ASSIGN(Visitor);
171
172  StringPool* pool_;
173  Pseudolocalizer::Method method_;
174  Pseudolocalizer localizer_;
175};
176
177ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base,
178                                              Pseudolocalizer::Method m) {
179  ConfigDescription modified = base;
180  switch (m) {
181    case Pseudolocalizer::Method::kAccent:
182      modified.language[0] = 'e';
183      modified.language[1] = 'n';
184      modified.country[0] = 'X';
185      modified.country[1] = 'A';
186      break;
187
188    case Pseudolocalizer::Method::kBidi:
189      modified.language[0] = 'a';
190      modified.language[1] = 'r';
191      modified.country[0] = 'X';
192      modified.country[1] = 'B';
193      break;
194    default:
195      break;
196  }
197  return modified;
198}
199
200void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
201                            ResourceConfigValue* original_value,
202                            StringPool* pool, ResourceEntry* entry) {
203  Visitor visitor(pool, method);
204  original_value->value->Accept(&visitor);
205
206  std::unique_ptr<Value> localized_value;
207  if (visitor.value) {
208    localized_value = std::move(visitor.value);
209  } else if (visitor.item) {
210    localized_value = std::move(visitor.item);
211  }
212
213  if (!localized_value) {
214    return;
215  }
216
217  ConfigDescription config_with_accent =
218      ModifyConfigForPseudoLocale(original_value->config, method);
219
220  ResourceConfigValue* new_config_value =
221      entry->FindOrCreateValue(config_with_accent, original_value->product);
222  if (!new_config_value->value) {
223    // Only use auto-generated pseudo-localization if none is defined.
224    new_config_value->value = std::move(localized_value);
225  }
226}
227
228/**
229 * A value is pseudolocalizable if it does not define a locale (or is the
230 * default locale)
231 * and is translateable.
232 */
233static bool IsPseudolocalizable(ResourceConfigValue* config_value) {
234  const int diff =
235      config_value->config.diff(ConfigDescription::DefaultConfig());
236  if (diff & ConfigDescription::CONFIG_LOCALE) {
237    return false;
238  }
239  return config_value->value->IsTranslateable();
240}
241
242}  // namespace
243
244bool PseudolocaleGenerator::Consume(IAaptContext* context,
245                                    ResourceTable* table) {
246  for (auto& package : table->packages) {
247    for (auto& type : package->types) {
248      for (auto& entry : type->entries) {
249        std::vector<ResourceConfigValue*> values =
250            entry->FindValuesIf(IsPseudolocalizable);
251
252        for (ResourceConfigValue* value : values) {
253          PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
254                                 &table->string_pool, entry.get());
255          PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
256                                 &table->string_pool, entry.get());
257        }
258      }
259    }
260  }
261  return true;
262}
263
264}  // namespace aapt
265