diff --git a/README.md b/README.md index b9101a8cbf4f..b47b497a9d95 100644 --- a/README.md +++ b/README.md @@ -610,9 +610,10 @@ Google Translate #### Preview Here's a snippet showing a simple usage example. The example shows how to detect the language of -some text and how to translate some text. The example assumes that the `GOOGLE_API_KEY` is set and -contains a valid API key. Alternatively, you can use the `apiKey(String)` setter in -`TranslateOptions.Builder` to set the API key. Complete source code can be found at +some text and how to translate some text. The example assumes that either default application +credentials or a valid api key are available. An api key stored in the `GOOGLE_API_KEY` environment +variable will be automatically detected. Alternatively, you can use the `apiKey(String)` setter in +`TranslateOptions.Builder`. Complete source code can be found at [DetectLanguageAndTranslate.java](./google-cloud-examples/src/main/java/com/google/cloud/examples/translate/snippets/DetectLanguageAndTranslate.java). ```java diff --git a/google-cloud-examples/src/main/java/com/google/cloud/examples/translate/snippets/DetectLanguageAndTranslate.java b/google-cloud-examples/src/main/java/com/google/cloud/examples/translate/snippets/DetectLanguageAndTranslate.java index 3da44edae015..5d506fe22fec 100644 --- a/google-cloud-examples/src/main/java/com/google/cloud/examples/translate/snippets/DetectLanguageAndTranslate.java +++ b/google-cloud-examples/src/main/java/com/google/cloud/examples/translate/snippets/DetectLanguageAndTranslate.java @@ -36,7 +36,8 @@ public class DetectLanguageAndTranslate { public static void main(String... args) { // Create a service object - // API key is read from the GOOGLE_API_KEY environment variable + // Default application credentials or an API key from the GOOGLE_API_KEY environment variable + // are used to authenticate requests Translate translate = TranslateOptions.getDefaultInstance().getService(); // Detect the language of some text diff --git a/google-cloud-translate/README.md b/google-cloud-translate/README.md index f1d32fe00619..ba646c8ce3b8 100644 --- a/google-cloud-translate/README.md +++ b/google-cloud-translate/README.md @@ -64,20 +64,25 @@ Getting Started --------------- #### Prerequisites For this tutorial, you need a [Google Developers Console](https://console.developers.google.com/) -project with "Translate API" enabled via the console's API Manager. You will also need a to enable -billing via the [Google Developers Console](https://console.developers.google.com/) project and to -retrieve an API key. See [Translate quickstart](https://cloud.google.com/translate/v2/quickstart) -for more details. +project with "Translate API" enabled via the console's API Manager. You also need to enable +billing via the [Google Developers Console](https://console.developers.google.com/). + +Finally, you must set up the local development environment by +[installing the Google Cloud SDK](https://cloud.google.com/sdk/) and running the following command +in command line: `gcloud auth application-default login`. Alternatively, you can authenticate +Translate requests using an API key. See +[Translate quickstart](https://cloud.google.com/translate/v2/quickstart) for more details. #### Installation and setup You'll need to obtain the `google-cloud-translate` library. See the [Quickstart](#quickstart) section to add `google-cloud-translate` as a dependency in your code. #### Creating an authorized service object -To make authenticated requests to Google Translates, you must create a service object with an API -key. By default, API key is looked for in the `GOOGLE_API_KEY` environment variable. Once the API -key is set, you can make API calls by invoking methods on the Translate service object. To create a -service object, given that `GOOGLE_API_KEY` is set, use the following code: +To make authenticated requests to Google Translate, you must create a service object with +credentials or with an API key. The simplest way to authenticate is to use +[Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials). +These credentials are automatically inferred from your environment, so you only need the following +code to create your service object: ```java import com.google.cloud.translate.Translate; @@ -86,7 +91,11 @@ import com.google.cloud.translate.TranslateOptions; Translate translate = TranslateOptions.getDefaultInstance().getService(); ``` -Or you can explicitly set the API key as follows: +Notice that this code can be also used with an API key. By default, an API key is looked for in the +`GOOGLE_API_KEY` environment variable. Once the API key is set, you can make API calls by invoking +methods on the Translate service created via `TranslateOptions.getDefaultInstance().getService()`. + +You can also explicitly set the API key as follows: ```java Translate translate = TranslateOptions.newBuilder().setApiKey("myKey").getService(); ``` @@ -131,8 +140,8 @@ Translation translation = translate.translate( In [DetectLanguageAndTranslate.java](../google-cloud-examples/src/main/java/com/google/cloud/examples/translate/snippets/DetectLanguageAndTranslate.java) -we put together all the code shown above into one program. The program assumes that a valid api key -is available. +we put together all the code shown above into one program. The program assumes that either default +application credentials or a valid api key are available. Troubleshooting --------------- diff --git a/google-cloud-translate/src/main/java/com/google/cloud/translate/Translate.java b/google-cloud-translate/src/main/java/com/google/cloud/translate/Translate.java index c56c879c8123..aa1d44036ae1 100644 --- a/google-cloud-translate/src/main/java/com/google/cloud/translate/Translate.java +++ b/google-cloud-translate/src/main/java/com/google/cloud/translate/Translate.java @@ -81,6 +81,19 @@ public static TranslateOption sourceLanguage(String sourceLanguage) { public static TranslateOption targetLanguage(String targetLanguage) { return new TranslateOption(TranslateRpc.Option.TARGET_LANGUAGE, targetLanguage); } + + /** + * Sets the language translation model. You can use this parameter to take advantage of Neural + * Machine Translation. Possible values are {@code base} and {@code nmt}. Google Translate could + * use a different model to translate your text, use {@link Translation#getModel()} to know + * which model was used for translation. Please notice that you must be whitelisted to use this + * option, otherwise translation will fail. + * + * @param model the language translation model + */ + public static TranslateOption model(String model) { + return new TranslateOption(TranslateRpc.Option.MODEL, model); + } } /** @@ -170,6 +183,8 @@ public static TranslateOption targetLanguage(String targetLanguage) { * @param texts the texts to translate * @return a list of objects containing information on the language translation, one for each * provided text, in order. + * @throws TranslateException upon failure or if {@link TranslateOption#model(String)} is used by + * a non-whitelisted user */ List translate(List texts, TranslateOption... options); @@ -189,6 +204,8 @@ public static TranslateOption targetLanguage(String targetLanguage) { * * @param text the text to translate * @return an object containing information on the language translation + * @throws TranslateException upon failure or if {@link TranslateOption#model(String)} is used by + * a non-whitelisted user */ Translation translate(String text, TranslateOption... options); } diff --git a/google-cloud-translate/src/main/java/com/google/cloud/translate/TranslateOptions.java b/google-cloud-translate/src/main/java/com/google/cloud/translate/TranslateOptions.java index acdb192b00c6..b7c1e40731a6 100644 --- a/google-cloud-translate/src/main/java/com/google/cloud/translate/TranslateOptions.java +++ b/google-cloud-translate/src/main/java/com/google/cloud/translate/TranslateOptions.java @@ -30,14 +30,17 @@ import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; public class TranslateOptions extends HttpServiceOptions { private static final long serialVersionUID = -572597134540398216L; + private static final String DEFAULT_HOST = "https://translation.googleapis.com"; private static final String API_KEY_ENV_NAME = "GOOGLE_API_KEY"; - private static final Set SCOPES = ImmutableSet.of(); + private static final Set SCOPES = + ImmutableSet.of("https://www.googleapis.com/auth/cloud-platform"); private final String apiKey; private final String targetLanguage; @@ -97,17 +100,6 @@ public Builder setProjectId(String projectId) { return self(); } - /** - * Sets the service authentication credentials. Setting credentials has no impact on the - * {@link Translate} service. - * - * @return the builder - */ - public Builder setCredentials(Credentials credentials) { - super.setCredentials(credentials); - return self(); - } - /** * Sets the API key used to issue requets. If not set, the API key is looked for in the * {@code GOOGLE_API_KEY} environment variable. For instructions on how to get an API key see @@ -158,8 +150,6 @@ public Builder setTargetLanguage(String targetLanguage) { @Override public TranslateOptions build() { - // Auth credentials are not used by Translate - setCredentials(NoCredentials.getInstance()); return new TranslateOptions(this); } } @@ -167,9 +157,6 @@ public TranslateOptions build() { private TranslateOptions(Builder builder) { super(TranslateFactory.class, TranslateRpcFactory.class, builder); this.apiKey = builder.apiKey != null ? builder.apiKey : getDefaultApiKey(); - checkArgument(this.apiKey != null, - "An API key is required for this service but could not be determined from the builder " - + "or the environment. Please set an API key using the builder."); this.targetLanguage = firstNonNull(builder.targetLanguage, Locale.ENGLISH.getLanguage()); } @@ -193,6 +180,11 @@ protected Set getScopes() { return SCOPES; } + @Override + protected String getDefaultHost() { + return DEFAULT_HOST; + } + @Deprecated protected String defaultApiKey() { return getDefaultApiKey(); @@ -250,8 +242,8 @@ public boolean equals(Object obj) { } TranslateOptions options = (TranslateOptions) obj; return baseEquals(options) - && apiKey.equals(options.apiKey) - && targetLanguage.equals(options.targetLanguage); + && Objects.equals(apiKey, options.apiKey) + && Objects.equals(targetLanguage, options.targetLanguage); } /** diff --git a/google-cloud-translate/src/main/java/com/google/cloud/translate/Translation.java b/google-cloud-translate/src/main/java/com/google/cloud/translate/Translation.java index fa79635a4a94..119f9ff67238 100644 --- a/google-cloud-translate/src/main/java/com/google/cloud/translate/Translation.java +++ b/google-cloud-translate/src/main/java/com/google/cloud/translate/Translation.java @@ -21,6 +21,7 @@ import com.google.common.base.MoreObjects; import java.io.Serializable; +import java.util.List; import java.util.Objects; /** @@ -43,10 +44,12 @@ public Translation apply(TranslationsResource translationPb) { private final String translatedText; private final String sourceLanguage; + private final String model; - private Translation(String translatedText, String sourceLanguage) { + private Translation(String translatedText, String sourceLanguage, String model) { this.translatedText = translatedText; this.sourceLanguage = sourceLanguage; + this.model = model; } /** @@ -81,6 +84,18 @@ public String getSourceLanguage() { return sourceLanguage; } + /** + * Returns the translation model used to translate the text. This value is only available if + * {@link Translate.TranslateOption#model(String)} was passed to + * {@link Translate#translate(List, Translate.TranslateOption...)}. + * + *

Please notice that you must be whitelisted to use the + * {@link Translate.TranslateOption#model(String)} option, otherwise translation will fail. + */ + public String getModel() { + return model; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -108,7 +123,8 @@ public final boolean equals(Object obj) { } static Translation fromPb(TranslationsResource translationPb) { + // TODO remove get("model") as soon as REST apiary supports model return new Translation(translationPb.getTranslatedText(), - translationPb.getDetectedSourceLanguage()); + translationPb.getDetectedSourceLanguage(), (String) translationPb.get("model")); } } diff --git a/google-cloud-translate/src/main/java/com/google/cloud/translate/spi/DefaultTranslateRpc.java b/google-cloud-translate/src/main/java/com/google/cloud/translate/spi/DefaultTranslateRpc.java index a5a7b4bc4763..0313234f5c7f 100644 --- a/google-cloud-translate/src/main/java/com/google/cloud/translate/spi/DefaultTranslateRpc.java +++ b/google-cloud-translate/src/main/java/com/google/cloud/translate/spi/DefaultTranslateRpc.java @@ -16,21 +16,28 @@ package com.google.cloud.translate.spi; +import static com.google.cloud.translate.spi.TranslateRpc.Option.MODEL; import static com.google.cloud.translate.spi.TranslateRpc.Option.SOURCE_LANGUAGE; import static com.google.cloud.translate.spi.TranslateRpc.Option.TARGET_LANGUAGE; import static com.google.common.base.MoreObjects.firstNonNull; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.json.JsonHttpContent; import com.google.api.client.json.jackson.JacksonFactory; import com.google.api.services.translate.Translate; +import com.google.api.services.translate.model.DetectionsListResponse; import com.google.api.services.translate.model.DetectionsResourceItems; +import com.google.api.services.translate.model.LanguagesListResponse; import com.google.api.services.translate.model.LanguagesResource; import com.google.api.services.translate.model.TranslationsResource; import com.google.cloud.translate.TranslateException; import com.google.cloud.translate.TranslateOptions; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import java.io.IOException; @@ -56,11 +63,27 @@ private static TranslateException translate(IOException exception) { return new TranslateException(exception); } + private GenericUrl buildTargetUrl(String path) { + GenericUrl genericUrl = new GenericUrl(translate.getBaseUrl() + "v2/" + path); + if (options.getApiKey() != null) { + genericUrl.put("key", options.getApiKey()); + } + return genericUrl; + } + @Override public List> detect(List texts) { try { + Map content = ImmutableMap.of("q", texts); + HttpRequest httpRequest = translate.getRequestFactory() + .buildPostRequest(buildTargetUrl("detect"), + new JsonHttpContent(translate.getJsonFactory(), content)) + .setParser(translate.getObjectParser()); List> detections = - translate.detections().list(texts).setKey(options.getApiKey()).execute().getDetections(); + httpRequest.execute().parseAs(DetectionsListResponse.class).getDetections(); + // TODO use REST apiary as soon as it supports POST + // List> detections = + // translate.detections().list(texts).setKey(options.getApiKey()).execute().getDetections(); return detections != null ? detections : ImmutableList.>of(); } catch (IOException ex) { throw translate(ex); @@ -70,12 +93,21 @@ public List> detect(List texts) { @Override public List listSupportedLanguages(Map optionMap) { try { - List languages = translate.languages() - .list() - .setKey(options.getApiKey()) - .setTarget( - firstNonNull(TARGET_LANGUAGE.getString(optionMap), options.getTargetLanguage())) - .execute().getLanguages(); + Map content = ImmutableMap.of("target", + firstNonNull(TARGET_LANGUAGE.getString(optionMap), options.getTargetLanguage())); + HttpRequest httpRequest = translate.getRequestFactory() + .buildPostRequest(buildTargetUrl("languages"), + new JsonHttpContent(translate.getJsonFactory(), content)) + .setParser(translate.getObjectParser()); + List languages = + httpRequest.execute().parseAs(LanguagesListResponse.class).getLanguages(); + // TODO use REST apiary as soon as it supports POST + // List languages = translate.languages() + // .list() + // .setKey(options.getApiKey()) + // .setTarget( + // firstNonNull(TARGET_LANGUAGE.getString(optionMap), options.getTargetLanguage())) + // .execute().getLanguages(); return languages != null ? languages : ImmutableList.of(); } catch (IOException ex) { throw translate(ex); @@ -85,6 +117,8 @@ public List listSupportedLanguages(Map optionMap) @Override public List translate(List texts, Map optionMap) { try { + // TODO use POST as soon as usage of "model" correctly reports an error in non-whitelisted + // projects String targetLanguage = firstNonNull(TARGET_LANGUAGE.getString(optionMap), options.getTargetLanguage()); final String sourceLanguage = SOURCE_LANGUAGE.getString(optionMap); @@ -93,6 +127,7 @@ public List translate(List texts, Map o .list(texts, targetLanguage) .setSource(sourceLanguage) .setKey(options.getApiKey()) + .set("model", MODEL.getString(optionMap)) .execute() .getTranslations(); return Lists.transform( diff --git a/google-cloud-translate/src/main/java/com/google/cloud/translate/spi/TranslateRpc.java b/google-cloud-translate/src/main/java/com/google/cloud/translate/spi/TranslateRpc.java index 5156bb7f1ac1..368f3505225c 100644 --- a/google-cloud-translate/src/main/java/com/google/cloud/translate/spi/TranslateRpc.java +++ b/google-cloud-translate/src/main/java/com/google/cloud/translate/spi/TranslateRpc.java @@ -27,7 +27,8 @@ public interface TranslateRpc { enum Option { SOURCE_LANGUAGE("source"), - TARGET_LANGUAGE("target"); + TARGET_LANGUAGE("target"), + MODEL("model"); private final String value; diff --git a/google-cloud-translate/src/test/java/com/google/cloud/translate/TranslateImplTest.java b/google-cloud-translate/src/test/java/com/google/cloud/translate/TranslateImplTest.java index cbf12dd6a03c..7e7aa0e0df17 100644 --- a/google-cloud-translate/src/test/java/com/google/cloud/translate/TranslateImplTest.java +++ b/google-cloud-translate/src/test/java/com/google/cloud/translate/TranslateImplTest.java @@ -86,9 +86,11 @@ public class TranslateImplTest { TranslateOption.targetLanguage("en"); private static final TranslateOption SOURCE_LANGUAGE_OPTION = TranslateOption.sourceLanguage("de"); + private static final TranslateOption MODEL_OPTION = TranslateOption.model("nmt"); private static final Map TRANSLATE_OPTIONS = ImmutableMap.of( TranslateRpc.Option.TARGET_LANGUAGE, TARGET_LANGUAGE_OPTION.getValue(), - TranslateRpc.Option.SOURCE_LANGUAGE, SOURCE_LANGUAGE_OPTION.getValue()); + TranslateRpc.Option.SOURCE_LANGUAGE, SOURCE_LANGUAGE_OPTION.getValue(), + TranslateRpc.Option.MODEL, "nmt"); private TranslateOptions options; private TranslateRpcFactory rpcFactoryMock; @@ -285,7 +287,7 @@ public void testTranslateWithOptions() { EasyMock.replay(translateRpcMock); initializeService(); assertEquals(TRANSLATION2, - translate.translate(text, TARGET_LANGUAGE_OPTION, SOURCE_LANGUAGE_OPTION)); + translate.translate(text, TARGET_LANGUAGE_OPTION, SOURCE_LANGUAGE_OPTION, MODEL_OPTION)); } @Test @@ -309,7 +311,7 @@ public void testTranslateListWithOptions() { EasyMock.replay(translateRpcMock); initializeService(); assertEquals(ImmutableList.of(TRANSLATION2), - translate.translate(texts, TARGET_LANGUAGE_OPTION, SOURCE_LANGUAGE_OPTION)); + translate.translate(texts, TARGET_LANGUAGE_OPTION, SOURCE_LANGUAGE_OPTION, MODEL_OPTION)); } @Test diff --git a/google-cloud-translate/src/test/java/com/google/cloud/translate/it/ITTranslateTest.java b/google-cloud-translate/src/test/java/com/google/cloud/translate/it/ITTranslateTest.java index 29931cd3c016..3de8041aa5f0 100644 --- a/google-cloud-translate/src/test/java/com/google/cloud/translate/it/ITTranslateTest.java +++ b/google-cloud-translate/src/test/java/com/google/cloud/translate/it/ITTranslateTest.java @@ -109,6 +109,20 @@ public void testTranslateTextList() { assertEquals("de", translation.getSourceLanguage()); } + @Test + public void testTranslateTextListWithModel() { + List translations = TRANSLATE.translate(ImmutableList.of("Hola", "Hallo"), + TranslateOption.model("nmt")); + Translation translation = translations.get(0); + assertEquals("Hello", translation.getTranslatedText()); + assertEquals("es", translation.getSourceLanguage()); + assertEquals("nmt", translation.getModel()); + translation = translations.get(1); + assertEquals("Hello", translation.getTranslatedText()); + assertEquals("de", translation.getSourceLanguage()); + assertEquals("nmt", translation.getModel()); + } + @Test public void testTranslateText() { Translation translation = TRANSLATE.translate("Hola");