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