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