diff options
Diffstat (limited to 'src/plugins/translation/src/com')
16 files changed, 2183 insertions, 0 deletions
diff --git a/src/plugins/translation/src/com/motorola/studio/android/json/JSONArray.java b/src/plugins/translation/src/com/motorola/studio/android/json/JSONArray.java new file mode 100644 index 0000000..91d2491 --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/json/JSONArray.java @@ -0,0 +1,92 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.motorola.studio.android.json;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+public class JSONArray extends JSONValue
+{
+ private final List<JSONValue> value;
+
+ public JSONArray(List<JSONValue> value)
+ {
+ this.value = value;
+ }
+
+ @Override
+ public List<JSONValue> getValue()
+ {
+ return value;
+ }
+
+ static JSONValue parseValues(List<Character> json)
+ {
+ Stack<Character> stack = new Stack<Character>();
+ List<JSONValue> values = new ArrayList<JSONValue>();
+ boolean parsed = false;
+ while (!parsed)
+ {
+ Character next = json.get(0);
+ if (next == '[')
+ {
+ json.remove(0);
+ stack.push('[');
+ }
+ else if (next == ']')
+ {
+ json.remove(0);
+ if (stack.pop() != '[')
+ {
+ throw new IllegalArgumentException();
+ }
+ else
+ {
+ parsed = true;
+ }
+ }
+ else if (next == ' ' || next == '\r' || next == '\n' || next == ',')
+ {
+ json.remove(0);
+ }
+ else
+ {
+ values.add(JSONValueParser.parse(json));
+ }
+ }
+ return new JSONArray(values);
+ }
+
+ @Override
+ public String toString()
+ {
+ String string = "[";
+ Iterator<JSONValue> objectIterator = value.iterator();
+ while (objectIterator.hasNext())
+ {
+ string += objectIterator.next().toString();
+ if (objectIterator.hasNext())
+ {
+ string += ",";
+ }
+ }
+ string += "]";
+
+ return string;
+ }
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/json/JSONBoolean.java b/src/plugins/translation/src/com/motorola/studio/android/json/JSONBoolean.java new file mode 100644 index 0000000..4332471 --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/json/JSONBoolean.java @@ -0,0 +1,77 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.motorola.studio.android.json;
+
+import java.util.List;
+
+public class JSONBoolean extends JSONValue
+{
+ public final boolean value;
+
+ public JSONBoolean(boolean value)
+ {
+ this.value = value;
+ }
+
+ @Override
+ public Object getValue()
+ {
+ return value;
+ }
+
+ static JSONValue parseValues(List<Character> json)
+ {
+ boolean parsed = false;
+ boolean value = false;
+
+ while (!parsed)
+ {
+ Character next = json.get(0);
+
+ if (next == 't' && json.get(1) == 'r' && json.get(2) == 'u' && json.get(3) == 'e')
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ json.remove(0);
+ }
+ parsed = true;
+ value = true;
+ }
+ else if (next == 'f' && json.get(1) == 'a' && json.get(2) == 'l' && json.get(3) == 's'
+ && json.get(4) == 'e')
+ {
+ for (int i = 0; i < 5; i++)
+ {
+ json.remove(0);
+ }
+ parsed = true;
+ }
+ else
+ {
+ throw new IllegalArgumentException();
+ }
+
+ }
+ return new JSONBoolean(value);
+ }
+
+ @Override
+ public String toString()
+ {
+ return Boolean.toString(value);
+ }
+
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/json/JSONNull.java b/src/plugins/translation/src/com/motorola/studio/android/json/JSONNull.java new file mode 100644 index 0000000..792bce3 --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/json/JSONNull.java @@ -0,0 +1,58 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.motorola.studio.android.json;
+
+import java.util.List;
+
+public class JSONNull extends JSONValue
+{
+ @Override
+ public Object getValue()
+ {
+ return null;
+ }
+
+ static JSONValue parseValues(List<Character> json)
+ {
+ boolean parsed = false;
+
+ while (!parsed)
+ {
+ Character next = json.get(0);
+
+ if (next == 'n' && json.get(1) == 'u' && json.get(2) == 'l' && json.get(3) == 'l')
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ json.remove(0);
+ }
+ parsed = true;
+ }
+ else
+ {
+ throw new IllegalArgumentException();
+ }
+
+ }
+ return new JSONNull();
+ }
+
+ @Override
+ public String toString()
+ {
+ return "null";
+ }
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/json/JSONNumber.java b/src/plugins/translation/src/com/motorola/studio/android/json/JSONNumber.java new file mode 100644 index 0000000..d10c9ca --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/json/JSONNumber.java @@ -0,0 +1,65 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.motorola.studio.android.json;
+
+import java.util.List;
+
+public class JSONNumber extends JSONValue
+{
+
+ private final int value;
+
+ public JSONNumber(int value)
+ {
+ this.value = value;
+ }
+
+ @Override
+ public Object getValue()
+ {
+ return value;
+ }
+
+ static JSONValue parseValues(List<Character> json)
+ {
+ boolean parsed = false;
+ StringBuilder number = new StringBuilder();
+
+ while (!parsed)
+ {
+ Character next = json.get(0);
+ if (next >= 48 && next <= 57)
+ {
+ json.remove(0);
+ number.append(next);
+ }
+ else
+ {
+ parsed = true;
+ }
+ }
+
+ return new JSONNumber(Integer.parseInt(number.toString()));
+
+ }
+
+ @Override
+ public String toString()
+ {
+ return Integer.toString(value);
+ }
+
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/json/JSONObject.java b/src/plugins/translation/src/com/motorola/studio/android/json/JSONObject.java new file mode 100644 index 0000000..5b64abe --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/json/JSONObject.java @@ -0,0 +1,95 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.motorola.studio.android.json;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+public class JSONObject extends JSONValue
+{
+ private final Set<JSONPair> value;
+
+ public JSONObject(Set<JSONPair> value)
+ {
+ this.value = value;
+ }
+
+ @Override
+ public Set<JSONPair> getValue()
+ {
+ return value;
+ }
+
+ static JSONValue parseValues(List<Character> json)
+ {
+ Stack<Character> stack = new Stack<Character>();
+ Set<JSONPair> values = new HashSet<JSONPair>();
+ boolean parsed = false;
+ while (!parsed)
+ {
+ Character next = json.get(0);
+
+ if (next == '{')
+ {
+ json.remove(0);
+ stack.push('{');
+ }
+ else if (next == '}')
+ {
+ json.remove(0);
+ if (stack.pop() != '{')
+ {
+ throw new IllegalArgumentException();
+ }
+ else
+ {
+ parsed = true;
+ }
+ }
+ else if (next == ' ' || next == '\r' || next == '\n' || next == ',')
+ {
+ json.remove(0);
+ }
+ else if (next == '"')
+ {
+ values.add(JSONPair.parse(json));
+ }
+ }
+ return new JSONObject(values);
+ }
+
+ @Override
+ public String toString()
+ {
+ String string = "{";
+ Iterator<JSONPair> objectIterator = value.iterator();
+ while (objectIterator.hasNext())
+ {
+ string += objectIterator.next().toString();
+ if (objectIterator.hasNext())
+ {
+ string += ",";
+ }
+ }
+ string += "}";
+
+ return string;
+ }
+
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/json/JSONPair.java b/src/plugins/translation/src/com/motorola/studio/android/json/JSONPair.java new file mode 100644 index 0000000..2a6424b --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/json/JSONPair.java @@ -0,0 +1,109 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.motorola.studio.android.json;
+
+import java.util.List;
+
+public class JSONPair
+{
+ private final String name;
+
+ private final JSONValue value;
+
+ public JSONPair(String name, JSONValue value)
+ {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public JSONValue getValue()
+ {
+ return value;
+ }
+
+ public static JSONPair parse(List<Character> json)
+ {
+ String name = null;
+ JSONValue value = null;
+ boolean parsed = false;
+ while (!parsed)
+ {
+ Character next = json.get(0);
+ if (next == '"')
+ {
+ name = parseName(json);
+ }
+ else if ((next == ' ') || (next == '\r') || (next == '\n'))
+ {
+ json.remove(0);
+ }
+ else if (next == ':')
+ {
+ json.remove(0);
+ value = JSONValueParser.parse(json);
+ parsed = true;
+ }
+ }
+
+ return new JSONPair(name, value);
+ }
+
+ private static String parseName(List<Character> json)
+ {
+ String name = null;
+ StringBuilder nameBuilder = new StringBuilder();
+ boolean specialChar = false;
+ Character next;
+ json.remove(0);
+ while (name == null)
+ {
+ next = json.remove(0);
+ if ((next == '"'))
+ {
+ if (specialChar)
+ {
+ specialChar = false;
+ }
+ else
+ {
+ name = nameBuilder.toString();
+ }
+ }
+ else if (next == '\\')
+ {
+ specialChar = true;
+ }
+ else
+ {
+ specialChar = false;
+ nameBuilder.append(next);
+ }
+ }
+ return name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "\"" + name + "\":" + value.toString();
+ }
+
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/json/JSONString.java b/src/plugins/translation/src/com/motorola/studio/android/json/JSONString.java new file mode 100644 index 0000000..c341bba --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/json/JSONString.java @@ -0,0 +1,94 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.motorola.studio.android.json;
+
+import java.util.List;
+
+public class JSONString extends JSONValue
+{
+ private final String value;
+
+ public JSONString(String value)
+ {
+ this.value = value;
+ }
+
+ @Override
+ public String getValue()
+ {
+ return value;
+ }
+
+ public static JSONValue parseValues(List<Character> json)
+ {
+ String value = null;
+ boolean parsed = false;
+ while (!parsed)
+ {
+ Character next = json.get(0);
+ if (next == '"')
+ {
+
+ value = parseName(json);
+ parsed = true;
+ }
+ }
+
+ return new JSONString(value);
+ }
+
+ private static String parseName(List<Character> json)
+ {
+ String name = null;
+ StringBuilder nameBuilder = new StringBuilder();
+ boolean specialChar = false;
+ Character next;
+ json.remove(0);
+ while (name == null)
+ {
+ next = json.remove(0);
+ if (next == '"')
+ {
+ if (specialChar)
+ {
+ specialChar = false;
+ nameBuilder.append(next);
+ }
+ else
+ {
+ name = nameBuilder.toString();
+ }
+ }
+ else if (next == '\\')
+ {
+ specialChar = true;
+ }
+ else
+ {
+ specialChar = false;
+ nameBuilder.append(next);
+ }
+ }
+ return name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "\"" + value + "\"";
+ }
+
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/json/JSONValue.java b/src/plugins/translation/src/com/motorola/studio/android/json/JSONValue.java new file mode 100644 index 0000000..4738177 --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/json/JSONValue.java @@ -0,0 +1,28 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.motorola.studio.android.json;
+
+import java.util.List;
+
+public abstract class JSONValue
+{
+ public abstract Object getValue();
+
+ static JSONValue parse(List<Character> json)
+ {
+ return null;
+ }
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/json/JSONValueParser.java b/src/plugins/translation/src/com/motorola/studio/android/json/JSONValueParser.java new file mode 100644 index 0000000..816d90c --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/json/JSONValueParser.java @@ -0,0 +1,74 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.motorola.studio.android.json;
+
+import java.util.List;
+
+public class JSONValueParser
+{
+ private JSONValueParser()
+ {
+ };
+
+ static JSONValue parse(List<Character> json)
+ {
+ JSONValue value = null;
+
+ boolean parsed = false;
+
+ while (!parsed)
+ {
+ Character next = json.get(0);
+
+ if (next == '{')
+ {
+ value = JSONObject.parse(json);
+ parsed = true;
+ }
+ else if (next == '[')
+ {
+ value = JSONArray.parse(json);
+ parsed = true;
+ }
+ else if (next == '"')
+ {
+ value = JSONString.parse(json);
+ parsed = true;
+ }
+ else if (next == 'n')
+ {
+ value = JSONNull.parse(json);
+ parsed = true;
+ }
+ else if (next == 't' || next == 'f')
+ {
+ value = JSONBoolean.parse(json);
+ parsed = true;
+ }
+ else if (next >= 48 && next <= 57)
+ {
+ value = JSONNumber.parse(json);
+ parsed = true;
+ }
+ else if (next == ' ' || next == '\r' || next == '\n')
+ {
+ json.remove(0);
+ }
+
+ }
+ return value;
+ }
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/json/Jason.java b/src/plugins/translation/src/com/motorola/studio/android/json/Jason.java new file mode 100644 index 0000000..0b0b039 --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/json/Jason.java @@ -0,0 +1,84 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.motorola.studio.android.json;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class is responsible to parse a JSON string into objects
+ * Visit {@link http://www.json.org/} for more information
+ *
+ */
+public class Jason
+{
+ private final Set<JSONObject> objects;
+
+ public Jason(String value)
+ {
+ objects = new HashSet<JSONObject>();
+ stip(value);
+ }
+
+ public Set<JSONObject> getJSON()
+ {
+ return objects;
+ }
+
+ private void stip(String value)
+ {
+ List<Character> json = new ArrayList<Character>();
+ for (char c : value.toCharArray())
+ {
+ json.add(c);
+ }
+
+ while (json.size() > 0)
+ {
+ Character next = json.get(0);
+
+ if (next == '{')
+ {
+ JSONObject object = (JSONObject) JSONObject.parse(json);
+ objects.add(object);
+ }
+ else if ((next == ' ') || (next == '\r') || (next == '\n') || (next == ','))
+ {
+ json.remove(0);
+ }
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ String string = "";
+ Iterator<JSONObject> objectIterator = objects.iterator();
+ while (objectIterator.hasNext())
+ {
+ string += objectIterator.next().toString();
+ if (objectIterator.hasNext())
+ {
+ string += ",";
+ }
+ }
+ return string;
+ }
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/localization/translators/GoogleTranslator.java b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/GoogleTranslator.java new file mode 100644 index 0000000..4e6c0a5 --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/GoogleTranslator.java @@ -0,0 +1,946 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.motorola.studio.android.localization.translators; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.Authenticator; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.eclipse.core.internal.net.ProxyManager; +import org.eclipse.core.net.proxy.IProxyData; +import org.eclipse.core.net.proxy.IProxyService; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.osgi.util.NLS; +import org.eclipse.sequoyah.localization.tools.datamodel.node.TranslationResult; +import org.eclipse.sequoyah.localization.tools.extensions.classes.ITranslator; +import org.eclipse.sequoyah.localization.tools.extensions.implementation.generic.ITranslateDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Link; + +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.common.proxy.ProxyAuthenticator; +import com.motorola.studio.android.common.utilities.EclipseUtils; +import com.motorola.studio.android.json.JSONArray; +import com.motorola.studio.android.json.JSONObject; +import com.motorola.studio.android.json.JSONPair; +import com.motorola.studio.android.json.JSONString; +import com.motorola.studio.android.json.JSONValue; +import com.motorola.studio.android.json.Jason; +import com.motorola.studio.android.localization.translators.i18n.TranslateNLS; + +/** + * Uses the Google translator web service (via executing a http request and + * parsing the answer) in order to translate a text string. + * + * Google v2 supports only one source and one destination per request, but support many words. + * + */ +@SuppressWarnings("restriction") +public final class GoogleTranslator extends ITranslator implements GoogleTranslatorConstants +{ + + /** + * Translate all words from some source language to destination language. + * This method handles any needed splits in original request due to API limitations. + * @param words the words being translated + * @param fromLanguage the origin language + * @param toLanguage the destination language + * @param monitor progress monitor + * @return a list of translation results + * @throws Exception + */ + public List<TranslationResult> translate(List<String> words, String fromLanguage, + String toLanguage, IProgressMonitor monitor) throws Exception + { + List<TranslationResult> translationResults = new ArrayList<TranslationResult>(); + int characterCount = 0; + int MAX_REQUEST_SIZE = getMaxQuerySize(fromLanguage, toLanguage); + + int maxRequestSize = MAX_REQUEST_SIZE; + List<String> wordsToTranslate = new ArrayList<String>(); + + Iterator<String> wordsIterator = words.iterator(); + int counter = 0; + + while (wordsIterator.hasNext()) + { + maxRequestSize -= STRING_PAR.length(); + String word = wordsIterator.next(); + /* try to add some more words to the request. + * If there is no more room left to request, execute the translation and continue afterwards + */ + if (characterCount + word.length() < maxRequestSize) + { + wordsToTranslate.add(word); + characterCount += word.length(); + } + else + { + URL translationURL = + createTranslationURL(wordsToTranslate, fromLanguage, toLanguage); + String httpRequestResponseBody = executeHttpGetRequest(translationURL); + + List<String> responses = parseTranslationResponse(httpRequestResponseBody); + + for (int i = 0; i < wordsToTranslate.size(); i++) + { + translationResults.add(new TranslationResult(words.get(counter++), this, + responses.get(i), fromLanguage, toLanguage, Calendar.getInstance() + .getTime(), true)); + } + characterCount = 0; + maxRequestSize = MAX_REQUEST_SIZE; + wordsToTranslate.clear(); + wordsToTranslate.add(word); + } + } + + /* + * execute the request with remaining sentences + */ + if (!wordsToTranslate.isEmpty()) + { + URL translationURL = createTranslationURL(wordsToTranslate, fromLanguage, toLanguage); + String httpRequestResponseBody = executeHttpGetRequest(translationURL); + List<String> responses = parseTranslationResponse(httpRequestResponseBody); + + for (int i = 0; i < wordsToTranslate.size(); i++) + { + translationResults.add(new TranslationResult(words.get(counter++), this, responses + .get(i), fromLanguage, toLanguage, Calendar.getInstance().getTime(), true)); + } + } + + return translationResults; + } + + /** + * Translates a string. + * + * @param text The String to be translated. + * @param from Original language. + * @param to Target language. + * @return The translated String. + * @throws Exception on errors. + */ + @Override + public TranslationResult translate(final String text, String from, String to) throws Exception + { + TranslationResult translationResult; + + if (text != null && !text.equals("") && text.length() < getMaxQuerySize(from, to)) //$NON-NLS-1$ + { + String httpResult = ""; //$NON-NLS-1$ + URL url = null; + + // Creates the URL to be used as request + try + { + List<String> sentences = new ArrayList<String>(); + sentences.add(text); + url = createTranslationURL(sentences, from, to); + httpResult = executeHttpGetRequest(url); + translationResult = + new TranslationResult(text, this, parseTranslationResponse(httpResult).get( + 0), from, to, Calendar.getInstance().getTime(), true); + } + catch (UnsupportedEncodingException e) + { + throw new HttpException(TranslateNLS.GoogleTranslator_Error_UnsupportedEncoding + + ENCODING_TYPE); + } + + } + else if (text.length() >= getMaxQuerySize(from, to)) + { + throw new Exception(TranslateNLS.GoogleTranslator_Error_QueryTooBig); + } + else + { + translationResult = new TranslationResult(text, this, text, from, to, new Date(), true); + } + + try + { + String descriptionToLog = + StudioLogger.KEY_TRANSLATION_PROVIDER + StudioLogger.VALUE_GOOGLE + + StudioLogger.SEPARATOR + StudioLogger.KEY_TRANSLATION_FROM_LANG + + from + StudioLogger.SEPARATOR + StudioLogger.KEY_TRANSLATION_TO_LANG + + to; + StudioLogger.collectUsageData(StudioLogger.WHAT_LOCALIZATION_AUTOMATICTRANSLATION, + StudioLogger.KIND_LOCALIZATION, descriptionToLog, TranslationPlugin.PLUGIN_ID, + TranslationPlugin.getDefault().getBundle().getVersion().toString()); + } + catch (Throwable t) + { + // Do nothing, usage data collection is for statistics and should not prevent tool from work + } + + return translationResult; + + } + + /** + * Translate a single word from one language to several other + * @param sentences sentence being translated + * @param fromLanguage source language + * @param toLanguages target languages + */ + @Override + public List<TranslationResult> translate(String sentence, String fromLanguage, + List<String> toLanguages) throws Exception + { + List<TranslationResult> translationResults = new ArrayList<TranslationResult>(); + + // Lets start with some checkings, one can never be too careful + if (fromLanguage == null || toLanguages == null || toLanguages.isEmpty()) + { + // We must have a FROM and a TO languages + throw new IllegalArgumentException( + TranslateNLS.GoogleTranslator_Error_ToAndFromLanguagesAreEmpty); + } + else if (sentence == null || sentence.equals("")) //$NON-NLS-1$ + { + // We must have something to be translated + sentence = ""; //$NON-NLS-1$ + if (toLanguages.size() == 1) + { + translationResults.add(new TranslationResult("", this, "", fromLanguage, //$NON-NLS-1$ //$NON-NLS-2$ + toLanguages.get(0), new Date(), true)); + } + } + else if (sentence.length() >= getMaxQuerySize(fromLanguage, toLanguages.get(0))) + { + throw new Exception(TranslateNLS.GoogleTranslator_Error_QueryTooBig); + } + /* + * Delegate the translation to another method + */ + else + { + for (String toLanguage : toLanguages) + { + translationResults.add(translate(sentence, fromLanguage, toLanguage)); + } + } + + try + { + // Collecting usage data for statistic purposes + String descriptionToLog = + StudioLogger.KEY_TRANSLATION_PROVIDER + StudioLogger.VALUE_GOOGLE + + StudioLogger.SEPARATOR + StudioLogger.KEY_TRANSLATION_FROM_LANG + + fromLanguage + StudioLogger.SEPARATOR + + StudioLogger.KEY_TRANSLATION_TO_LANG + "several languages"; //$NON-NLS-1$ + + StudioLogger.collectUsageData(StudioLogger.WHAT_LOCALIZATION_AUTOMATICTRANSLATION, + StudioLogger.KIND_LOCALIZATION, descriptionToLog, TranslationPlugin.PLUGIN_ID, + TranslationPlugin.getDefault().getBundle().getVersion().toString()); + } + catch (Throwable t) + { + // Do nothing, usage data collection is for statistics and should not prevent tool from work + } + + return translationResults; + } + + /** + * Translate a list of sentences from one language to another + * @param sentences sentences being translated + * @param fromLanguage source language + * @param toLanguage target language + * @param monitor progress monitor + */ + @Override + public List<TranslationResult> translateAll(List<String> sentences, String fromLanguage, + String toLanguage, IProgressMonitor monitor) throws Exception + { + // The result (duhh) + List<TranslationResult> translationResults = new ArrayList<TranslationResult>(); + + // Lets start with some checkings, one can never be too carefull + if (fromLanguage == null || toLanguage == null) + { + // We must have a FROM and a TO languages + throw new IllegalArgumentException( + TranslateNLS.GoogleTranslator_Error_ToAndFromLanguagesAreEmpty); + } + else if (sentences == null || sentences.size() == 0) + { + // We must have something to be translated + throw new IllegalArgumentException(TranslateNLS.GoogleTranslator_Error_NoAvailableData); + } + else + { + translationResults.addAll(translate(sentences, fromLanguage, toLanguage, monitor)); + } + + try + { + // Collecting usage data for statistic purposes + String descriptionToLog = + StudioLogger.KEY_TRANSLATION_PROVIDER + StudioLogger.VALUE_GOOGLE + + StudioLogger.SEPARATOR + StudioLogger.KEY_TRANSLATION_FROM_LANG + + fromLanguage + StudioLogger.SEPARATOR + + StudioLogger.KEY_TRANSLATION_TO_LANG + toLanguage; + + StudioLogger.collectUsageData(StudioLogger.WHAT_LOCALIZATION_AUTOMATICTRANSLATION, + StudioLogger.KIND_LOCALIZATION, descriptionToLog, TranslationPlugin.PLUGIN_ID, + TranslationPlugin.getDefault().getBundle().getVersion().toString()); + } + catch (Throwable t) + { + // Do nothing, usage data collection is for statistics and should not prevent tool from work + } + + return translationResults; + } + + /** + * Translates a list of strings from a list of given languages to other given languages (given by a list, or course), + * using google Ajax API's for that. + * + * The three lists have the same number of elements. + * + * This comment feels like the "Three Swatch watch switching witches watched switched Swatch watch witches switch", + * but I'll let it here anyway. + */ + @Override + public List<TranslationResult> translateAll(List<String> words, List<String> fromLanguage, + List<String> toLanguage, IProgressMonitor monitor) throws Exception + { + // The result (duhh) + List<TranslationResult> translationResults = new ArrayList<TranslationResult>(); + + // Lets start with some checkings, one can never be too carefull + if (fromLanguage == null || toLanguage == null) + { + // We must have a FROM and a TO languages + throw new IllegalArgumentException( + TranslateNLS.GoogleTranslator_Error_ToAndFromLanguagesAreEmpty); + } + else if (words == null || words.size() == 0) + { + // We must have something to be translated + throw new IllegalArgumentException(TranslateNLS.GoogleTranslator_Error_NoAvailableData); + } + else + { + translationResults.addAll(groupAndTranslate(words, fromLanguage, toLanguage, monitor)); + } + + try + { + // Collecting usage data for statistic purposes + String descriptionToLog = + StudioLogger.KEY_TRANSLATION_PROVIDER + StudioLogger.VALUE_GOOGLE + + StudioLogger.SEPARATOR + StudioLogger.KEY_TRANSLATION_FROM_LANG + + fromLanguage + StudioLogger.SEPARATOR + + StudioLogger.KEY_TRANSLATION_TO_LANG + toLanguage; + + StudioLogger.collectUsageData(StudioLogger.WHAT_LOCALIZATION_AUTOMATICTRANSLATION, + StudioLogger.KIND_LOCALIZATION, descriptionToLog, TranslationPlugin.PLUGIN_ID, + TranslationPlugin.getDefault().getBundle().getVersion().toString()); + } + catch (Throwable t) + { + // Do nothing, usage data collection is for statistics and should not prevent tool from work + } + + return translationResults; + } + + private List<TranslationResult> groupAndTranslate(List<String> words, + List<String> fromLanguage, List<String> toLanguage, IProgressMonitor monitor) + throws Exception + { + + List<TranslationResult> results = new ArrayList<TranslationResult>(); + /* + * Get all words with same source and same destination and group them to make translation more efficient + * Notice that this implementation relies on input condition that all lists have the same size and all elements with same index makes one request + */ + + class StringItem + { + private String sentence = ""; + + private int orderNumber = -1; + + public StringItem(String sentence, int orderNumber) + { + this.sentence = sentence; + this.orderNumber = orderNumber; + } + + public Integer getOrderNumber() + { + return orderNumber; + } + + public String getSentence() + { + return sentence; + } + + @Override + public String toString() + { + return orderNumber + "|" + sentence; + } + } + + /* + * This map holds a list of group of stringitems being translated. These items have the same from and to languages + * Using linkedHashMap to keep insertionOrder + */ + Map<String, List<StringItem>> sameSourceDestMap = + new LinkedHashMap<String, List<StringItem>>(); + Map<Integer, TranslationResult> translations = new HashMap<Integer, TranslationResult>(); + + // group things with same from and to languages + for (int i = 0; i < words.size(); i++) + { + /* + * Check if one of the words are big enough to not be translated + */ + if (words.get(i).length() >= getMaxQuerySize(fromLanguage.get(i), toLanguage.get(i)) + - STRING_PAR.length() - 1) + { + throw new Exception(TranslateNLS.GoogleTranslator_Error_QueryTooBig); + } + + String key = fromLanguage.get(i) + "|" + toLanguage.get(i); + List<StringItem> itemsToTranslate = sameSourceDestMap.get(key); + if (itemsToTranslate == null) + { + itemsToTranslate = new ArrayList<StringItem>(); + sameSourceDestMap.put(key, itemsToTranslate); + } + itemsToTranslate.add(new StringItem(words.get(i), i)); + } + + for (String key : sameSourceDestMap.keySet()) + { + List<StringItem> itemsToTranslate = sameSourceDestMap.get(key); + List<String> items = new ArrayList<String>(); + for (StringItem item : itemsToTranslate) + { + items.add(item.getSentence()); + } + List<TranslationResult> tempResults = + translate(items, key.split("\\|")[0], key.split("\\|")[1], monitor); + for (int i = 0; i < itemsToTranslate.size(); i++) + { + translations.put(itemsToTranslate.get(i).getOrderNumber(), tempResults.get(i)); + } + + } + + for (int i = 0; i < words.size(); i++) + { + results.add(translations.get(i)); + } + + return results; + } + + private int getMaxQuerySize(String fromLanguage, String toLanguage) + { + return MAX_QUERY_SIZE - TRANSLATE_URL_WITHOUT_PARAMS.length() - API_KEY_PARAM.length() + - getApiKey().length() - SOURCE_PAR.length() - fromLanguage.length() + - TARGET_PARAM.length() - toLanguage.length(); + } + + /** + * Parse the translation response of the http request + * @param httpRequestResponseBody the response body + * @param sourceLanguage the source language + * @param destinationLanguage the destination language + * @return a list of String objects for the strings translated for source/destination languages pair + */ + private List<String> parseTranslationResponse(String httpRequestResponseBody) + { + JSONPair translationSection = getTranslationSection(httpRequestResponseBody); + + return getTranslations(translationSection); + } + + /* + * { + "data": { + "translations": [ + { + "translatedText": "bla bla bla" + }, + { + "translatedText": "foo bar" + } + ] + } + } + */ + private JSONPair getTranslationSection(String httpRequestResponseBody) + { + Jason ripper = new Jason(httpRequestResponseBody); + JSONPair translationsSection = null; + Iterator<JSONObject> jsonIterator = ripper.getJSON().iterator(); + while (translationsSection == null && jsonIterator.hasNext()) + { + translationsSection = findPair(jsonIterator.next(), TRANSLATIONS_SECTION); + } + + return translationsSection; + } + + private static JSONPair findPair(JSONValue origin, String name) + { + JSONPair pair = null; + if (origin instanceof JSONObject) + { + JSONObject object = (JSONObject) origin; + Iterator<JSONPair> pairs = object.getValue().iterator(); + while (pair == null && pairs.hasNext()) + { + JSONPair jsonPair = pairs.next(); + if (jsonPair.getName().equals(name)) + { + pair = jsonPair; + } + else + { + pair = findPair(jsonPair.getValue(), name); + } + + } + } + + return pair; + } + + private List<String> getTranslations(JSONPair translationSection) + { + List<String> translations = new ArrayList<String>(); + if (translationSection.getValue() instanceof JSONArray) + { + JSONArray translationsArray = (JSONArray) translationSection.getValue(); + for (JSONValue translationObject : translationsArray.getValue()) + { + translations.add(getTranslation(translationObject)); + } + } + + return translations; + } + + /** + * @param translationObject + * { + * "translatedText": "Hallo Welt" + * } + * @return pure translation + * Hallo Welt + */ + private String getTranslation(JSONValue translationObject) + { + String translation = null; + if (translationObject instanceof JSONObject) + { + JSONObject jsonObject = (JSONObject) translationObject; + translation = + ((JSONString) jsonObject.getValue().iterator().next().getValue()).getValue(); + } + return translation != null ? fixHTMLTags(translation) : null; + } + + private URL createTranslationURL(List<String> wordsToTranslate, String fromLanguage, + String toLanguage) throws UnsupportedEncodingException + { + URL translationURL = null; + + // We need to unescape the ' (apostrophe) before sending it to translation + String regex = "\\\\'"; //$NON-NLS-1$ + Pattern pattern = Pattern.compile(regex); + + StringBuilder urlBuilder = new StringBuilder(TRANSLATE_URL_WITHOUT_PARAMS); + urlBuilder.append(URL_PARAMETERS.replace("#FROM#", fromLanguage) + .replace("#TO#", toLanguage).replace("#API_KEY#", getApiKey())); + + for (String word : wordsToTranslate) + { + String wordToTranslate = pattern.matcher(word).replaceAll("'"); + urlBuilder.append(STRING_PAR); + urlBuilder.append(URLEncoder.encode(wordToTranslate, ENCODING_TYPE)); + } + + try + { + translationURL = new URL(urlBuilder.toString()); + } + catch (MalformedURLException e) + { + StudioLogger.error(getClass(), "Unable to create translation URL", e); + } + + return translationURL; + } + + /** + * The Android localization files text must accept three HTML tags: i, b and u. + * Nevertheless, google translator returns the close part of this tags with + * a extra-space that makes the sintax wrong. This method will try to fix it. + * @param originalText + * @return the text with the tags fixed + */ + private static String fixHTMLTags(String originalText) + { + String result = ""; //$NON-NLS-1$ + if (originalText != null) + { + result = originalText; + } + result = originalText.replaceAll("</ b>", "</b>"); //$NON-NLS-1$ //$NON-NLS-2$ + result = originalText.replaceAll("</ i>", "</i>"); //$NON-NLS-1$ //$NON-NLS-2$ + result = originalText.replaceAll("</ u>", "</u>"); //$NON-NLS-1$ //$NON-NLS-2$ + + result = originalText.replaceAll("</ B>", "</B>"); //$NON-NLS-1$ //$NON-NLS-2$ + result = originalText.replaceAll("</ I>", "</I>"); //$NON-NLS-1$ //$NON-NLS-2$ + result = originalText.replaceAll("</ U>", "</U>"); //$NON-NLS-1$ //$NON-NLS-2$ + + result = originalText.replaceAll(" \\\\ n ", " \\\\n "); //$NON-NLS-1$ //$NON-NLS-2$ + + return result; + } + + /** + * Creates an HTTP request with the URL, execute it as a get, and returns + * the a string with the result. + * + * @param url + * URL to be executed. + * @return String with the URL execution result. + * @throws IOException + * If an exception occurs on transport + * @throws HttpException + * If an exception occurs on the protocol + * @throws Exception + * on error. + */ + protected static String executeHttpGetRequest(final URL url) throws HttpException + { + + // Checking query size due to google policies + if (url.toString().length() > MAX_QUERY_SIZE) + { + throw new HttpException(TranslateNLS.GoogleTranslator_Error_QueryTooBig); + } + + // Try to retrieve proxy configuration to use if necessary + IProxyService proxyService = ProxyManager.getProxyManager(); + IProxyData proxyData = null; + if (proxyService.isProxiesEnabled() || proxyService.isSystemProxiesEnabled()) + { + Authenticator.setDefault(new ProxyAuthenticator()); + String urlStr = url.toString(); + if (urlStr.startsWith("https")) + { + proxyData = proxyService.getProxyData(IProxyData.HTTPS_PROXY_TYPE); + StudioLogger.debug(GoogleTranslator.class, "Using https proxy"); //$NON-NLS-1$ + } + else if (urlStr.startsWith("http")) + { + proxyData = proxyService.getProxyData(IProxyData.HTTP_PROXY_TYPE); + StudioLogger.debug(GoogleTranslator.class, "Using http proxy"); //$NON-NLS-1$ + } + else + { + StudioLogger.debug(GoogleTranslator.class, "Not using any proxy"); //$NON-NLS-1$ + } + } + + // Creates the http client and the method to be executed + HttpClient client = null; + client = new HttpClient(); + + // If there is proxy data, work with it + if (proxyData != null) + { + if (proxyData.getHost() != null) + { + // Sets proxy host and port, if any + client.getHostConfiguration().setProxy(proxyData.getHost(), proxyData.getPort()); + } + + if (proxyData.getUserId() != null && proxyData.getUserId().trim().length() > 0) + { + // Sets proxy user and password, if any + Credentials cred = + new UsernamePasswordCredentials(proxyData.getUserId(), + proxyData.getPassword() == null ? "" : proxyData.getPassword()); //$NON-NLS-1$ + client.getState().setProxyCredentials(AuthScope.ANY, cred); + } + } + + // Creating the method to be executed, the URL at this point is enough + // because it is complete + GetMethod method = new GetMethod(url.toString()); + + // Set method to be retried three times in case of error + method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, + new DefaultHttpMethodRetryHandler(RETRIES, false)); + + method.setRequestHeader(REFERER_HEADER, REFERER_SITE); + + // Set the connection timeout + client.getHttpConnectionManager().getParams().setConnectionTimeout(new Integer(TIMEOUT)); + + String result = ""; //$NON-NLS-1$ + try + { + // Execute the method. + int statusCode; + try + { + statusCode = client.executeMethod(method); + result = method.getResponseBodyAsString(MAX_SIZE); + } + catch (IOException e) + { + throw new HttpException(TranslateNLS.GoogleTranslator_Error_CannotConnectToServer + + e.getMessage()); + } + + checkStatusCode(statusCode, result); + + // Unescape any possible unicode char + result = unescapeUnicode(result); + + // Unescape any possible HTML sequence + result = unescapeHTML(result); + + } + + finally + { + // Release the connection. + method.releaseConnection(); + } + + return result; + } + + private static void checkStatusCode(int statusCode, String response) throws HttpException + { + switch (statusCode) + { + case HttpStatus.SC_OK: + //do nothing + break; + case HttpStatus.SC_BAD_REQUEST: + throw new HttpException(NLS.bind( + TranslateNLS.GoogleTranslator_ErrorMessageExecutingRequest, + getErrorMessage(response))); + + case HttpStatus.SC_REQUEST_URI_TOO_LONG: + throw new HttpException(TranslateNLS.GoogleTranslator_Error_QueryTooBig); + + case HttpStatus.SC_FORBIDDEN: + throw new HttpException(NLS.bind( + TranslateNLS.GoogleTranslator_ErrorMessageNoValidTranslationReturned, + getErrorMessage(response))); + + default: + throw new HttpException(NLS.bind( + TranslateNLS.GoogleTranslator_Error_HTTPRequestError, new Object[] + { + statusCode, getErrorMessage(response) + })); + + } + } + + /** + * According to APIv2, the error message is in the end of the response + * { + "error": { + "errors": [ + { + "domain": "global", + "reason": "invalid", + "message": "Invalid Value" + } + ], + "code": 400, + "message": "Invalid Value" + } + } + * @param response the method response body + * @return the error message + */ + private static String getErrorMessage(String response) + { + Jason ripper = new Jason(response); + JSONPair translationsSection = null; + Iterator<JSONObject> jsonIterator = ripper.getJSON().iterator(); + while (translationsSection == null && jsonIterator.hasNext()) + { + translationsSection = findPair(jsonIterator.next(), MESSAGE_TEXT); + } + + return translationsSection != null ? ((JSONString) translationsSection.getValue()) + .getValue() : null; + } + + /** + * Unescape any HTML sequence that exists inside the string. For example, + * the sequence ' will be changed to the ' symbol + * + * @param source + * original text + * @return the result + */ + private static String unescapeHTML(String source) + { + Pattern p = Pattern.compile("&#([0-9]+);"); //$NON-NLS-1$ + String result = source; + Matcher m = p.matcher(result); + while (m.find()) + { + char c = (char) Integer.parseInt(m.group(1)); + if (c == "'".charAt(0)) //$NON-NLS-1$ + { + // Apostrophes must be escaped by preceding it with a backslash (\) on the XML file + result = result.replaceAll(m.group(0), "\\\\'"); //$NON-NLS-1$ + } + else + { + result = result.replaceAll(m.group(0), "" + c); //$NON-NLS-1$ + } + } + + return result; + } + + /** + * Unescape any Unicode sequence that exists inside the string. + * + * For example, the sequence \u0000 will be changed to the symbol + * correnponded to the 0000 unicode value. + * + * @param source + * original text + * @return the result + */ + private static String unescapeUnicode(String source) + { + int i = 0, len = source.length(); + char c; + StringBuffer buffer = new StringBuffer(len); + while (i < len) + { + c = source.charAt(i++); + if (c == '\\') + { + if (i < len) + { + c = source.charAt(i++); + if (c == 'u') + { + c = (char) Integer.parseInt(source.substring(i, i + 4), 16); + i += 4; + } + } + } + buffer.append(c); + } + return buffer.toString(); + } + + private static String getApiKey() + { + String apiKey = GoogleTranslatorConstants.API_KEY; + IPreferenceStore prefStore = TranslationPlugin.getDefault().getPreferenceStore(); + if (!prefStore.isDefault(GoogleTranslatorConstants.API_KEY_VALUE_PREFERENCE)) + { + apiKey = prefStore.getString(GoogleTranslatorConstants.API_KEY_VALUE_PREFERENCE); + if (apiKey == null) + { + apiKey = GoogleTranslatorConstants.API_KEY; + } + } + + return apiKey; + } + + @Override + public Composite createCustomArea(Composite parent, final ITranslateDialog dialog) + { + Composite mainComposite = new Composite(parent, SWT.NONE); + mainComposite.setLayout(new GridLayout(1, false)); + + final Link prefPageLink = new Link(mainComposite, SWT.NONE); + prefPageLink.setText(TranslateNLS.GoogleTranslator_ChangeAPIkeyLabel); + prefPageLink.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true, 1, 1)); + prefPageLink.addSelectionListener(new SelectionAdapter() + { + @Override + public void widgetSelected(SelectionEvent e) + { + EclipseUtils.openPreference(prefPageLink.getShell(), + "com.motorola.studio.android.localization.translators.preferencepage"); //$NON-NLS-1$ + dialog.validate(); + } + }); + mainComposite.setLayoutData(new GridData(SWT.RIGHT, SWT.BOTTOM, false, false)); + return mainComposite; + } + + @Override + public String canTranslate(String fromLanguage, String[] toLanguages) + { + return getApiKey() == null || GoogleTranslatorConstants.API_KEY.equals(getApiKey()) + ? TranslateNLS.GoogleTranslator_ErrorNoAPIkeySet : null; + } +}
\ No newline at end of file diff --git a/src/plugins/translation/src/com/motorola/studio/android/localization/translators/GoogleTranslatorConstants.java b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/GoogleTranslatorConstants.java new file mode 100644 index 0000000..b6730c9 --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/GoogleTranslatorConstants.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.motorola.studio.android.localization.translators; + +import org.eclipse.sequoyah.localization.tools.extensions.implementation.generic.TranslatorConstants; + +/** + * Constants used by the GoogleTranslator class + * and its users. + */ +public interface GoogleTranslatorConstants extends TranslatorConstants +{ + + //API Key to identify MOTODEV site + static final String API_KEY = + "ABQIAAAAzLP1ONRdncTVQSc4T1g2MRT7zv61Fj6qXODo5OU8i7eIwTs2GRQjZ2moI_dkzMckcgUZys7M9wAMvQ"; + + public static final String API_KEY_VALUE_PREFERENCE = "google.translator.apikey"; + + // Encoding type + public static final String ENCODING_TYPE = "UTF-8"; + + /** + * Parameter used to create URL + */ + public static final String SOURCE_PAR = "&source="; + + /** + * Parameter used to create URL + */ + public static final String STRING_PAR = "&q="; + + /** + * Parameter used to create URL + */ + public static final String TARGET_PARAM = "&target="; + + /** + * Parameter used to create URL + */ + public static final String API_KEY_PARAM = "&key="; + + /** + * The base URL to access the translation service. + * the #FROM# and #TO# parts are replaced on execution time. + */ + public static final String URL_PARAMETERS = API_KEY_PARAM + "#API_KEY#" + SOURCE_PAR + "#FROM#" + + TARGET_PARAM + "#TO#"; + + /** + * The base URL to access the translation service, without the parameters. + * The parameters are created on real time when needed. + */ + public static final String TRANSLATE_URL_WITHOUT_PARAMS = + "https://www.googleapis.com/language/translate/v2?prettyprint=false"; + + /** + * The base URL to access the translation service. + * the #FROM# and #TO# parts are replaced on execution time. + */ + public static final String BASE_TRANSLATE_URL = TRANSLATE_URL_WITHOUT_PARAMS + URL_PARAMETERS; + + /** + * Text that appears just before the translated text begins + * on a typical answer from the web server + */ + public static final String TRANSLATED_TEXT_KEY = "translatedText"; + + /** + * Text defining the translation section of response + */ + public static final String TRANSLATIONS_SECTION = "translations"; + + /** + * Text defining the error section of response + */ + public static final String ERROR_SECTION = "error"; + + /** + * Text defining the message section of response + */ + public static final String MESSAGE_TEXT = "message"; + + //Max string size + public static int MAX_SIZE = 100000; + + //Max string size + public static int MAX_QUERY_SIZE = 2000; // google rules!! Do not change without VT + + //Number of retries for the http request when there are connection problems + public static final int RETRIES = 0; + + //Timeout in miliseconds for the http queries + public static final int TIMEOUT = 4000; + + //HTTP Header constant to set the referer + public static final String REFERER_HEADER = "Referer"; + + // Site to be used as a referer site + public static final String REFERER_SITE = "http://developer.motorola.com"; + +} diff --git a/src/plugins/translation/src/com/motorola/studio/android/localization/translators/TranslationPlugin.java b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/TranslationPlugin.java new file mode 100644 index 0000000..f97b55d --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/TranslationPlugin.java @@ -0,0 +1,77 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.motorola.studio.android.localization.translators;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+import com.motorola.studio.android.common.log.StudioLogger;
+
+public class TranslationPlugin extends AbstractUIPlugin
+{
+
+ /**
+ * The plug-in ID
+ */
+ public static final String PLUGIN_ID = "com.motorola.studio.android.translation";
+
+ // The shared instance
+ private static TranslationPlugin plugin;
+
+ public TranslationPlugin()
+ {
+ plugin = this;
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static TranslationPlugin getDefault()
+ {
+ return plugin;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+ */
+ @Override
+ public void start(BundleContext context) throws Exception
+ {
+ StudioLogger.debug(TranslationPlugin.class,
+ "Starting MOTODEV Studio for Android Translation Plugin...");
+
+ super.start(context);
+
+ StudioLogger.debug(TranslationPlugin.class,
+ "MOTODEV Studio for Android Translation Plugin started.");
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+ */
+ @Override
+ public void stop(BundleContext context) throws Exception
+ {
+ plugin = null;
+ super.stop(context);
+ }
+
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/localization/translators/i18n/TranslateNLS.java b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/i18n/TranslateNLS.java new file mode 100644 index 0000000..32dc8fa --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/i18n/TranslateNLS.java @@ -0,0 +1,58 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.motorola.studio.android.localization.translators.i18n;
+
+import org.eclipse.osgi.util.NLS;
+
+public class TranslateNLS extends NLS
+{
+ static
+ {
+ NLS.initializeMessages(
+ "com.motorola.studio.android.localization.translators.i18n.translateNLS", //$NON-NLS-1$
+ TranslateNLS.class);
+ }
+
+ public static String GoogleTranslator_ChangeAPIkeyLabel;
+
+ public static String GoogleTranslator_Error_CannotConnectToServer;
+
+ public static String GoogleTranslator_Error_HTTPRequestError;
+
+ public static String GoogleTranslator_Error_NoAvailableData;
+
+ public static String GoogleTranslator_Error_QueryTooBig;
+
+ public static String GoogleTranslator_Error_ToAndFromLanguagesAreEmpty;
+
+ public static String GoogleTranslator_Error_UnsupportedEncoding;
+
+ public static String GoogleTranslator_ErrorMessageExecutingRequest;
+
+ public static String GoogleTranslator_ErrorMessageNoValidTranslationReturned;
+
+ public static String GoogleTranslator_ErrorNoAPIkeySet;
+
+ public static String AndroidPreferencePage_googleApiKey_GroupLabel;
+
+ public static String AndroidPreferencePage_googleApiKey_Label;
+
+ public static String AndroidPreferencePage_googleApiKey_Note;
+
+ public static String AndroidPreferencePage_googleApiKey_Tooltip;
+
+}
diff --git a/src/plugins/translation/src/com/motorola/studio/android/localization/translators/i18n/translateNLS.properties b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/i18n/translateNLS.properties new file mode 100644 index 0000000..5eb661f --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/i18n/translateNLS.properties @@ -0,0 +1,15 @@ +GoogleTranslator_ChangeAPIkeyLabel=<a>Change your API key</a>
+GoogleTranslator_Error_CannotConnectToServer=Cannot connect to HTTP server:
+GoogleTranslator_Error_HTTPRequestError=Answer for the HTTP request was not OK. Error code: {0}. Error message: {1}.
+GoogleTranslator_Error_NoAvailableData=List of words to be translated cannot be empty
+GoogleTranslator_Error_QueryTooBig=Cannot execute query; query text is too long
+GoogleTranslator_Error_ToAndFromLanguagesAreEmpty='To' and 'From' languages cannot be empty
+GoogleTranslator_Error_UnsupportedEncoding=Unsupported encoding type:
+GoogleTranslator_ErrorMessageExecutingRequest=An error occurred trying to execute your translation request: {0}
+GoogleTranslator_ErrorMessageNoValidTranslationReturned=The translation service did not return a valid translation. The daily limit for accessing the Google Translate API may have been reached or your API key is wrong.\nService response message is: {0}
+GoogleTranslator_ErrorNoAPIkeySet=You must enter your own API key
+
+AndroidPreferencePage_googleApiKey_GroupLabel=Google Translate API
+AndroidPreferencePage_googleApiKey_Label=API Key:
+AndroidPreferencePage_googleApiKey_Note=Note: to obtain your own API key, go to the Google APIs Console at <a>{0}</a>
+AndroidPreferencePage_googleApiKey_Tooltip=Supply the API key to be used to access the Google Translate API
\ No newline at end of file diff --git a/src/plugins/translation/src/com/motorola/studio/android/localization/translators/preferences/ui/TranslationPreferencePage.java b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/preferences/ui/TranslationPreferencePage.java new file mode 100644 index 0000000..95904c0 --- /dev/null +++ b/src/plugins/translation/src/com/motorola/studio/android/localization/translators/preferences/ui/TranslationPreferencePage.java @@ -0,0 +1,196 @@ +/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.motorola.studio.android.localization.translators.preferences.ui;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.browser.IWebBrowser;
+import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
+import org.osgi.service.prefs.BackingStoreException;
+
+import com.motorola.studio.android.common.log.StudioLogger;
+import com.motorola.studio.android.localization.translators.GoogleTranslatorConstants;
+import com.motorola.studio.android.localization.translators.TranslationPlugin;
+import com.motorola.studio.android.localization.translators.i18n.TranslateNLS;
+
+public class TranslationPreferencePage extends PreferencePage implements IWorkbenchPreferencePage
+{
+ private final String GOOGLE_APIS_CONSOLE_LINK = "http://code.google.com/apis/console/"; //$NON-NLS-1$
+
+ private Text apiKeyText;
+
+ private String apiKeyValue;
+
+ public void init(IWorkbench workbench)
+ {
+ noDefaultAndApplyButton();
+ }
+
+ @Override
+ public boolean performOk()
+ {
+ InstanceScope scope = (InstanceScope) InstanceScope.INSTANCE;
+ IEclipsePreferences prefs = scope.getNode(TranslationPlugin.PLUGIN_ID);
+ if (apiKeyValue.trim().length() == 0)
+ {
+ prefs.remove(GoogleTranslatorConstants.API_KEY_VALUE_PREFERENCE);
+ }
+ else
+ {
+ prefs.put(GoogleTranslatorConstants.API_KEY_VALUE_PREFERENCE, apiKeyValue.trim());
+ }
+
+ try
+ {
+ prefs.flush();
+ }
+ catch (BackingStoreException e)
+ {
+ //do nothing
+ }
+
+ return super.performOk();
+ }
+
+ @Override
+ protected Control createContents(Composite parent)
+ {
+ Composite entryTable = new Composite(parent, SWT.NULL);
+ GridData data = new GridData(SWT.FILL, SWT.TOP, true, false);
+ entryTable.setLayoutData(data);
+
+ GridLayout layout = new GridLayout();
+ entryTable.setLayout(layout);
+
+ layout = new GridLayout(2, false);
+
+ layout = new GridLayout(2, false);
+ Group translatorAPIGroup = new Group(entryTable, SWT.NONE);
+ translatorAPIGroup.setLayout(layout);
+ translatorAPIGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+ translatorAPIGroup.setText(TranslateNLS.AndroidPreferencePage_googleApiKey_GroupLabel);
+
+ Link noteLabel = new Link(translatorAPIGroup, SWT.WRAP);
+ noteLabel.setText(TranslateNLS.bind(TranslateNLS.AndroidPreferencePage_googleApiKey_Note,
+ GOOGLE_APIS_CONSOLE_LINK));
+ data = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1);
+ data.widthHint = 450;
+ noteLabel.setLayoutData(data);
+
+ noteLabel.addSelectionListener(new SelectionAdapter()
+ {
+ /* (non-Javadoc)
+ * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
+ */
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ IWorkbenchBrowserSupport browserSupport =
+ PlatformUI.getWorkbench().getBrowserSupport();
+
+ /*
+ * open the browser
+ */
+ IWebBrowser browser;
+ try
+ {
+ browser =
+ browserSupport.createBrowser(IWorkbenchBrowserSupport.LOCATION_BAR
+ | IWorkbenchBrowserSupport.NAVIGATION_BAR
+ | IWorkbenchBrowserSupport.AS_EXTERNAL, "MOTODEV", null, null); //$NON-NLS-1$
+
+ browser.openURL(new URL(GOOGLE_APIS_CONSOLE_LINK));
+ }
+ catch (PartInitException ex)
+ {
+ StudioLogger.error("Error opening the Google APIs Console link: " //$NON-NLS-1$
+ + ex.getMessage());
+ }
+ catch (MalformedURLException ex)
+ {
+ StudioLogger.error("Error opening the Google APIs Console link: " //$NON-NLS-1$
+ + ex.getMessage());
+ }
+ }
+ });
+
+ Label apiKeyLabel = new Label(translatorAPIGroup, SWT.NONE);
+ apiKeyLabel.setText(TranslateNLS.AndroidPreferencePage_googleApiKey_Label);
+ data = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
+ apiKeyLabel.setLayoutData(data);
+
+ apiKeyText = new Text(translatorAPIGroup, SWT.BORDER);
+ apiKeyValue = getApiKey();
+ apiKeyText.setText(apiKeyValue);
+ apiKeyText.setToolTipText(TranslateNLS.AndroidPreferencePage_googleApiKey_Tooltip);
+ data = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1);
+ apiKeyText.setLayoutData(data);
+ apiKeyText.addModifyListener(new ModifyListener()
+ {
+ public void modifyText(ModifyEvent e)
+ {
+ apiKeyValue = apiKeyText.getText();
+
+ }
+ });
+
+ return entryTable;
+ }
+
+ /**
+ * get the apikey
+ * @return the apikey or an empty string for the default one
+ */
+ private static String getApiKey()
+ {
+ String apiKey = ""; //$NON-NLS-1$
+ IPreferenceStore prefStore = TranslationPlugin.getDefault().getPreferenceStore();
+ if (!prefStore.isDefault(GoogleTranslatorConstants.API_KEY_VALUE_PREFERENCE))
+ {
+ apiKey = prefStore.getString(GoogleTranslatorConstants.API_KEY_VALUE_PREFERENCE);
+ if (apiKey == null)
+ {
+ apiKey = ""; //$NON-NLS-1$
+ }
+ }
+
+ return apiKey;
+ }
+
+}
|