xref: /aosp_15_r20/external/zxing/javase/src/main/java/com/google/zxing/client/j2se/StringsResourceTranslator.java (revision 513427e33d61bc67fc40bc261642ac0b2a686b45)
1 /*
2  * Copyright 2010 ZXing authors
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 package com.google.zxing.client.j2se;
18 
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.InputStreamReader;
22 import java.io.Writer;
23 import java.net.URI;
24 import java.net.URLConnection;
25 import java.net.URLEncoder;
26 import java.nio.charset.StandardCharsets;
27 import java.nio.file.DirectoryStream;
28 import java.nio.file.Files;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.nio.file.StandardCopyOption;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.TreeMap;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
40 
41 /**
42  * <p>A utility which auto-translates English strings in Android string resources using
43  * Google Translate.</p>
44  *
45  * <p>Pass the Android client res/ directory as first argument, and optionally message keys
46  * who should be forced to retranslate.
47  * Usage: {@code StringsResourceTranslator android/res/ [key_1 ...]}</p>
48  *
49  * <p>You must set your Google Translate API key into the environment with -DtranslateAPI.key=...</p>
50  *
51  * @author Sean Owen
52  * @deprecated without replacement since 3.4.2
53  */
54 @Deprecated
55 public final class StringsResourceTranslator {
56 
57   private static final String API_KEY = System.getProperty("translateAPI.key");
58   static {
59     if (API_KEY == null) {
60       throw new IllegalArgumentException("translateAPI.key is not specified");
61     }
62   }
63 
64   private static final Pattern ENTRY_PATTERN = Pattern.compile("<string name=\"([^\"]+)\".*>([^<]+)</string>");
65   private static final Pattern STRINGS_FILE_NAME_PATTERN = Pattern.compile("values-(.+)");
66   private static final Pattern TRANSLATE_RESPONSE_PATTERN = Pattern.compile("translatedText\":\\s*\"([^\"]+)\"");
67   private static final Pattern VALUES_DIR_PATTERN = Pattern.compile("values-[a-z]{2}(-[a-zA-Z]{2,3})?");
68 
69   private static final String APACHE_2_LICENSE =
70       "<!--\n" +
71       " Copyright (C) 2015 ZXing authors\n" +
72       '\n' +
73       " Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
74       " you may not use this file except in compliance with the License.\n" +
75       " You may obtain a copy of the License at\n" +
76       '\n' +
77       "      http://www.apache.org/licenses/LICENSE-2.0\n" +
78       '\n' +
79       " Unless required by applicable law or agreed to in writing, software\n" +
80       " distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
81       " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
82       " See the License for the specific language governing permissions and\n" +
83       " limitations under the License.\n" +
84       " -->\n";
85 
86   private static final Map<String,String> LANGUAGE_CODE_MASSAGINGS = new HashMap<>(3);
87   static {
88     LANGUAGE_CODE_MASSAGINGS.put("zh-rCN", "zh-cn");
89     LANGUAGE_CODE_MASSAGINGS.put("zh-rTW", "zh-tw");
90   }
91 
StringsResourceTranslator()92   private StringsResourceTranslator() {}
93 
main(String[] args)94   public static void main(String[] args) throws IOException {
95     Path resDir = Paths.get(args[0]);
96     Path valueDir = resDir.resolve("values");
97     Path stringsFile = valueDir.resolve("strings.xml");
98     Collection<String> forceRetranslation = Arrays.asList(args).subList(1, args.length);
99 
100     DirectoryStream.Filter<Path> filter = entry ->
101         Files.isDirectory(entry) && !Files.isSymbolicLink(entry) &&
102         VALUES_DIR_PATTERN.matcher(entry.getFileName().toString()).matches();
103     try (DirectoryStream<Path> dirs = Files.newDirectoryStream(resDir, filter)) {
104       for (Path dir : dirs) {
105         translate(stringsFile, dir.resolve("strings.xml"), forceRetranslation);
106       }
107     }
108   }
109 
translate(Path englishFile, Path translatedFile, Collection<String> forceRetranslation)110   private static void translate(Path englishFile,
111                                 Path translatedFile,
112                                 Collection<String> forceRetranslation) throws IOException {
113 
114     Map<String, String> english = readLines(englishFile);
115     Map<String,String> translated = readLines(translatedFile);
116     String parentName = translatedFile.getParent().getFileName().toString();
117 
118     Matcher stringsFileNameMatcher = STRINGS_FILE_NAME_PATTERN.matcher(parentName);
119     if (!stringsFileNameMatcher.find()) {
120       throw new IllegalArgumentException("Invalid parent dir: " + parentName);
121     }
122     String language = stringsFileNameMatcher.group(1);
123     String massagedLanguage = LANGUAGE_CODE_MASSAGINGS.get(language);
124     if (massagedLanguage != null) {
125       language = massagedLanguage;
126     }
127 
128     System.out.println("Translating " + language);
129 
130     Path resultTempFile = Files.createTempFile(null, null);
131 
132     boolean anyChange = false;
133     try (Writer out = Files.newBufferedWriter(resultTempFile, StandardCharsets.UTF_8)) {
134       out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
135       out.write(APACHE_2_LICENSE);
136       out.write("<resources>\n");
137 
138       for (Map.Entry<String,String> englishEntry : english.entrySet()) {
139         String key = englishEntry.getKey();
140         String value = englishEntry.getValue();
141         out.write("  <string name=\"");
142         out.write(key);
143         out.write('"');
144         if (value.contains("%s") || value.contains("%f")) {
145           // Need to specify that there's a value placeholder
146           out.write(" formatted=\"false\"");
147         }
148         out.write('>');
149 
150         String translatedString = translated.get(key);
151         if (translatedString == null || forceRetranslation.contains(key)) {
152           anyChange = true;
153           translatedString = translateString(value, language);
154           // Specially for string resources, escape ' with \
155           translatedString = translatedString.replaceAll("'", "\\\\'");
156         }
157         out.write(translatedString);
158 
159         out.write("</string>\n");
160       }
161 
162       out.write("</resources>\n");
163       out.flush();
164     }
165 
166     if (anyChange) {
167       System.out.println("  Writing translations");
168       Files.move(resultTempFile, translatedFile, StandardCopyOption.REPLACE_EXISTING);
169     } else {
170       Files.delete(resultTempFile);
171     }
172   }
173 
translateString(String english, String language)174   static String translateString(String english, String language) throws IOException {
175     if ("en".equals(language)) {
176       return english;
177     }
178     String massagedLanguage = LANGUAGE_CODE_MASSAGINGS.get(language);
179     if (massagedLanguage != null) {
180       language = massagedLanguage;
181     }
182     System.out.println("  Need translation for " + english);
183 
184     URI translateURI = URI.create(
185         "https://www.googleapis.com/language/translate/v2?key=" + API_KEY + "&q=" +
186             URLEncoder.encode(english, "UTF-8") +
187             "&source=en&target=" + language);
188     CharSequence translateResult = fetch(translateURI);
189     Matcher m = TRANSLATE_RESPONSE_PATTERN.matcher(translateResult);
190     if (!m.find()) {
191       System.err.println("No translate result");
192       System.err.println(translateResult);
193       return english;
194     }
195     String translation = m.group(1);
196 
197     // This is a little crude; unescape some common escapes in the raw response
198     translation = translation.replaceAll("&(amp;)?quot;", "\"");
199     translation = translation.replaceAll("&(amp;)?#39;", "'");
200 
201     System.out.println("  Got translation " + translation);
202     return translation;
203   }
204 
fetch(URI translateURI)205   private static CharSequence fetch(URI translateURI) throws IOException {
206     URLConnection connection = translateURI.toURL().openConnection();
207     connection.connect();
208     StringBuilder translateResult = new StringBuilder(200);
209     try (BufferedReader in =
210            new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
211       char[] buffer = new char[8192];
212       int charsRead;
213       while ((charsRead = in.read(buffer)) > 0) {
214         translateResult.append(buffer, 0, charsRead);
215       }
216     }
217     return translateResult;
218   }
219 
readLines(Path file)220   private static Map<String,String> readLines(Path file) throws IOException {
221     if (Files.exists(file)) {
222       Map<String,String> entries = new TreeMap<>();
223       for (String line : Files.readAllLines(file, StandardCharsets.UTF_8)) {
224         Matcher m = ENTRY_PATTERN.matcher(line);
225         if (m.find()) {
226           entries.put(m.group(1), m.group(2));
227         }
228       }
229       return entries;
230     } else {
231       return Collections.emptyMap();
232     }
233   }
234 
235 }
236