diff --git a/globalization/en-GB.xml b/globalization/english.xml similarity index 86% rename from globalization/en-GB.xml rename to globalization/english.xml index 581fe4ce..7637b7db 100644 --- a/globalization/en-GB.xml +++ b/globalization/english.xml @@ -1,12 +1,14 @@ - + + + diff --git a/src/api/AnnotationPostProcessor.java b/src/api/AnnotationPostProcessor.java index 0de6cb3a..b72cfe49 100644 --- a/src/api/AnnotationPostProcessor.java +++ b/src/api/AnnotationPostProcessor.java @@ -1,20 +1,196 @@ package api; +import globalization.ContextPaths; +import globalization.Translator; import io.swagger.v3.jaxrs2.Reader; import io.swagger.v3.jaxrs2.ReaderListener; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.responses.ApiResponse; +import static java.util.Arrays.asList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; public class AnnotationPostProcessor implements ReaderListener { + private interface TranslatableProperty { + public String keyName(); + public void setValue(T item, String translation); + public String getValue(T item); + } + + private class ContextInformation { + public String path; + public Map keys; + } + + private static final String TRANSLATION_EXTENTION_NAME = "x-translation"; + + private static final List> translatableInfoProperties = asList( + new TranslatableProperty() { + @Override public String keyName() { return "description.key"; } + @Override public void setValue(Info item, String translation) { item.setDescription(translation); } + @Override public String getValue(Info item) { return item.getDescription(); } + }, + new TranslatableProperty() { + @Override public String keyName() { return "title.key"; } + @Override public void setValue(Info item, String translation) { item.setTitle(translation); } + @Override public String getValue(Info item) { return item.getTitle(); } + }, + new TranslatableProperty() { + @Override public String keyName() { return "termsOfService.key"; } + @Override public void setValue(Info item, String translation) { item.setTermsOfService(translation); } + @Override public String getValue(Info item) { return item.getTermsOfService(); } + } + ); + + private static final List> translatablePathItemProperties = asList( + new TranslatableProperty() { + @Override public String keyName() { return "description.key"; } + @Override public void setValue(PathItem item, String translation) { item.setDescription(translation); } + @Override public String getValue(PathItem item) { return item.getDescription(); } + }, + new TranslatableProperty() { + @Override public String keyName() { return "summary.key"; } + @Override public void setValue(PathItem item, String translation) { item.setSummary(translation); } + @Override public String getValue(PathItem item) { return item.getSummary(); } + } + ); + + private static final List> translatableOperationProperties = asList( + new TranslatableProperty() { + @Override public String keyName() { return "description.key"; } + @Override public void setValue(Operation item, String translation) { item.setDescription(translation); } + @Override public String getValue(Operation item) { return item.getDescription(); } + }, + new TranslatableProperty() { + @Override public String keyName() { return "summary.key"; } + @Override public void setValue(Operation item, String translation) { item.setSummary(translation); } + @Override public String getValue(Operation item) { return item.getSummary(); } + } + ); + + private static final List> translatableApiResponseProperties = asList( + new TranslatableProperty() { + @Override public String keyName() { return "description.key"; } + @Override public void setValue(ApiResponse item, String translation) { item.setDescription(translation); } + @Override public String getValue(ApiResponse item) { return item.getDescription(); } + } + ); + + private final Translator translator; + + public AnnotationPostProcessor() { + this(Translator.getInstance()); + } + + public AnnotationPostProcessor(Translator translator) { + this.translator = translator; + + + } + @Override public void beforeScan(Reader reader, OpenAPI openAPI) {} @Override public void afterScan(Reader reader, OpenAPI openAPI) { - // TODO: use context path and keys from "x-translation" extension annotations - // to translate "descriptions" and finally remove "x-translation" extensions - // from output. + // use context path and keys from "x-translation" extension annotations + // to translate supported annotations and finally remove "x-translation" extensions + Info resourceInfo = openAPI.getInfo(); + ContextInformation resourceContext = getContextInformation(openAPI.getExtensions()); + removeTranslationAnnotations(openAPI.getExtensions()); + TranslateProperty(translatableInfoProperties, resourceContext, resourceInfo); + + for (Map.Entry pathEntry : openAPI.getPaths().entrySet()) + { + PathItem pathItem = pathEntry.getValue(); + ContextInformation pathContext = getContextInformation(pathItem.getExtensions(), resourceContext); + removeTranslationAnnotations(pathItem.getExtensions()); + TranslateProperty(translatablePathItemProperties, pathContext, pathItem); + + for (Operation operation : pathItem.readOperations()) { + ContextInformation operationContext = getContextInformation(operation.getExtensions(), pathContext); + removeTranslationAnnotations(operation.getExtensions()); + TranslateProperty(translatableOperationProperties, operationContext, operation); + + for (Map.Entry responseEntry : operation.getResponses().entrySet()) { + ApiResponse response = responseEntry.getValue(); + ContextInformation responseContext = getContextInformation(response.getExtensions(), operationContext); + removeTranslationAnnotations(response.getExtensions()); + TranslateProperty(translatableApiResponseProperties, responseContext, response); + } + } + } + } + + private void TranslateProperty(List> translatableProperties, ContextInformation context, T item) { + if(context.keys != null) { + Map keys = context.keys; + for(TranslatableProperty prop : translatableProperties) { + String key = keys.get(prop.keyName()); + if(key != null) { + String originalValue = prop.getValue(item); + String translation = translator.translate(Locale.ENGLISH, context.path, key, originalValue); + prop.setValue(item, translation); + } + } + } + } + + private ContextInformation getContextInformation(Map extensions) { + return getContextInformation(extensions, null); + } + + private ContextInformation getContextInformation(Map extensions, ContextInformation base) { + if(extensions != null) { + Map translationDefinitions = (Map)extensions.get(TRANSLATION_EXTENTION_NAME); + if(translationDefinitions != null) { + ContextInformation result = new ContextInformation(); + result.path = getAbsolutePath(base, (String)translationDefinitions.get("path")); + result.keys = getTranslationKeys(translationDefinitions); + return result; + } + } + + if(base != null) { + ContextInformation result = new ContextInformation(); + result.path = base.path; + return result; + } + + return null; + } + + private void removeTranslationAnnotations(Map extensions) { + if(extensions == null) + return; + + extensions.remove(TRANSLATION_EXTENTION_NAME); } + private Map getTranslationKeys(Map translationDefinitions) { + Map result = new HashMap<>(); + + for(TranslatableProperty prop : translatableInfoProperties) { + String key = (String)translationDefinitions.get(prop.keyName()); + if(key != null) + result.put(prop.keyName(), key); + } + + return result; + } + + private String getAbsolutePath(ContextInformation base, String path) { + String result = (base != null) ? base.path : "/"; + path = (path != null) ? path : ""; + result = ContextPaths.combinePaths(result, path); + return result; + } } diff --git a/src/api/BlocksResource.java b/src/api/BlocksResource.java index 37c2d2a8..b9110212 100644 --- a/src/api/BlocksResource.java +++ b/src/api/BlocksResource.java @@ -24,7 +24,7 @@ import repository.RepositoryManager; @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) @OpenAPIDefinition( extensions = @Extension(name = "translation", properties = { - @ExtensionProperty(name="path", value="/BlocksResource"), + @ExtensionProperty(name="path", value="/Api/BlocksResource") }) ) public class BlocksResource { @@ -47,7 +47,7 @@ public class BlocksResource { description = "Returns an array of the 50 last blocks generated by your accounts", extensions = @Extension(name = "translation", properties = { @ExtensionProperty(name="path", value="getBlocks"), - @ExtensionProperty(name="key", value="description") + @ExtensionProperty(name="description.key", value="description") }), responses = { @ApiResponse( @@ -58,7 +58,7 @@ public class BlocksResource { responseCode = "422", description = "Error: 201 - Wallet does not exist", extensions = @Extension(name = "translation", properties = { - @ExtensionProperty(name="key", value="ApiError/201") + @ExtensionProperty(name="description.key", value="ApiError/201") }), content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)) ) diff --git a/src/globalization/Translator.java b/src/globalization/Translator.java index fa9a17f2..91d4bb6b 100644 --- a/src/globalization/Translator.java +++ b/src/globalization/Translator.java @@ -73,6 +73,7 @@ public class Translator { Logger.getLogger(Translator.class.getName()).log(Level.SEVERE, String.format("Duplicate entry for locale '%s' and path '%s' in translation file '%s'. Falling back to default translations.", entry.locale(), entry.path(), file)); return; } + localTranslations.put(entry.path(), entry.template()); } }