diff --git a/globalization/Api.de.xml b/globalization/Api.de.xml
new file mode 100644
index 00000000..b188fa51
--- /dev/null
+++ b/globalization/Api.de.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/globalization/Api.en.xml b/globalization/Api.en.xml
new file mode 100644
index 00000000..3becbd6e
--- /dev/null
+++ b/globalization/Api.en.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/globalization/BlocksResource.de.xml b/globalization/BlocksResource.de.xml
new file mode 100644
index 00000000..4d3c8ab8
--- /dev/null
+++ b/globalization/BlocksResource.de.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/globalization/BlocksResource.en.xml b/globalization/BlocksResource.en.xml
new file mode 100644
index 00000000..2d39c70e
--- /dev/null
+++ b/globalization/BlocksResource.en.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/globalization/english.xml b/globalization/english.xml
deleted file mode 100644
index 7637b7db..00000000
--- a/globalization/english.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/api/AnnotationPostProcessor.java b/src/api/AnnotationPostProcessor.java
index 51ffcc27..94b66b30 100644
--- a/src/api/AnnotationPostProcessor.java
+++ b/src/api/AnnotationPostProcessor.java
@@ -8,82 +8,19 @@ 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() {
@@ -92,8 +29,6 @@ public class AnnotationPostProcessor implements ReaderListener {
public AnnotationPostProcessor(Translator translator) {
this.translator = translator;
-
-
}
@Override
@@ -106,25 +41,25 @@ public class AnnotationPostProcessor implements ReaderListener {
Info resourceInfo = openAPI.getInfo();
ContextInformation resourceContext = getContextInformation(openAPI.getExtensions());
removeTranslationAnnotations(openAPI.getExtensions());
- TranslateProperty(translatableInfoProperties, resourceContext, resourceInfo);
+ TranslateProperty(Constants.TRANSLATABLE_INFO_PROPERTIES, 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);
+ TranslateProperty(Constants.TRANSLATABLE_PATH_ITEM_PROPERTIES, pathContext, pathItem);
for (Operation operation : pathItem.readOperations()) {
ContextInformation operationContext = getContextInformation(operation.getExtensions(), pathContext);
removeTranslationAnnotations(operation.getExtensions());
- TranslateProperty(translatableOperationProperties, operationContext, operation);
+ TranslateProperty(Constants.TRANSLATABLE_OPERATION_PROPERTIES, 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);
+ TranslateProperty(Constants.TRANSLATABLE_API_RESPONSE_PROPERTIES, responseContext, response);
}
}
}
@@ -137,8 +72,8 @@ public class AnnotationPostProcessor implements ReaderListener {
String key = keys.get(prop.keyName());
if(key != null) {
String originalValue = prop.getValue(item);
- // XXX: use configurable or browser locale instead english?
- String translation = translator.translate(Locale.ENGLISH, context.path, key, originalValue);
+ // XXX: use browser locale instead default?
+ String translation = translator.translate(context.path, key, originalValue);
prop.setValue(item, translation);
}
}
@@ -151,10 +86,10 @@ public class AnnotationPostProcessor implements ReaderListener {
private ContextInformation getContextInformation(Map extensions, ContextInformation base) {
if(extensions != null) {
- Map translationDefinitions = (Map)extensions.get(TRANSLATION_EXTENTION_NAME);
+ Map translationDefinitions = (Map)extensions.get("x-" + Constants.TRANSLATION_EXTENSION_NAME);
if(translationDefinitions != null) {
ContextInformation result = new ContextInformation();
- result.path = getAbsolutePath(base, (String)translationDefinitions.get("path"));
+ result.path = combinePaths(base, (String)translationDefinitions.get(Constants.TRANSLATION_PATH_EXTENSION_NAME));
result.keys = getTranslationKeys(translationDefinitions);
return result;
}
@@ -173,13 +108,13 @@ public class AnnotationPostProcessor implements ReaderListener {
if(extensions == null)
return;
- extensions.remove(TRANSLATION_EXTENTION_NAME);
+ extensions.remove("x-" + Constants.TRANSLATION_EXTENSION_NAME);
}
private Map getTranslationKeys(Map translationDefinitions) {
Map result = new HashMap<>();
- for(TranslatableProperty prop : translatableInfoProperties) {
+ for(TranslatableProperty prop : Constants.TRANSLATABLE_INFO_PROPERTIES) {
String key = (String)translationDefinitions.get(prop.keyName());
if(key != null)
result.put(prop.keyName(), key);
@@ -188,10 +123,8 @@ public class AnnotationPostProcessor implements ReaderListener {
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;
+ private String combinePaths(ContextInformation base, String path) {
+ String basePath = (base != null) ? base.path : null;
+ return ContextPaths.combinePaths(basePath, path);
}
}
diff --git a/src/api/ApiClient.java b/src/api/ApiClient.java
index d43a63f2..67f1f859 100644
--- a/src/api/ApiClient.java
+++ b/src/api/ApiClient.java
@@ -1,16 +1,20 @@
package api;
+import globalization.ContextPaths;
import globalization.Translator;
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.extensions.Extension;
+import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
+import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.Path;
@@ -49,6 +53,8 @@ public class ApiClient {
}
}
+ private static final String TRANSLATION_CONTEXT_PATH = "/Api/ApiClient";
+
private static final Pattern COMMAND_PATTERN = Pattern.compile("^ *(?GET|POST|PUT|PATCH|DELETE) *(?.*)$");
private static final Pattern HELP_COMMAND_PATTERN = Pattern.compile("^ *help *(?.*)$", Pattern.CASE_INSENSITIVE);
private static final List> HTTP_METHOD_ANNOTATIONS = Arrays.asList(
@@ -59,8 +65,8 @@ public class ApiClient {
DELETE.class
);
+ private final Translator translator;
ApiService apiService;
- private Translator translator;
List helpInfos;
public ApiClient(ApiService apiService, Translator translator) {
@@ -94,18 +100,28 @@ public class ApiClient {
if (resourcePath == null) {
continue;
}
-
String resourcePathString = resourcePath.value();
+ // get translation context path for resource
+ String resourceContextPath = "/";
+ OpenAPIDefinition openAPIDefinition = resource.getDeclaredAnnotation(OpenAPIDefinition.class);
+ if(openAPIDefinition != null)
+ resourceContextPath = getContextPath(openAPIDefinition.extensions());
+
// scan each method
for (Method method : resource.getDeclaredMethods()) {
Operation operationAnnotation = method.getAnnotation(Operation.class);
- if (operationAnnotation == null) {
+ if (operationAnnotation == null)
continue;
- }
String description = operationAnnotation.description();
+ // translate
+ String operationContextPath = ContextPaths.combinePaths(resourceContextPath, getContextPath(operationAnnotation.extensions()));
+ String operationDescriptionKey = getDescriptionTranslationKey(operationAnnotation.extensions());
+ if(operationDescriptionKey != null)
+ description = translator.translate(operationContextPath, operationDescriptionKey, description);
+
// extract responses
ArrayList success = new ArrayList();
ArrayList errors = new ArrayList();
@@ -114,6 +130,20 @@ public class ApiClient {
if(StringUtils.isBlank(responseDescription))
continue; // ignore responses without description
+ // translate
+ String responseContextPath = ContextPaths.combinePaths(operationContextPath, getContextPath(response.extensions()));
+ String responseDescriptionKey = getDescriptionTranslationKey(response.extensions());
+ if(responseDescriptionKey != null)
+ responseDescription = translator.translate(responseContextPath, responseDescriptionKey, responseDescription);
+
+ String apiErrorCode = getApiErrorCode(response.extensions());
+ if(apiErrorCode != null) {
+ responseDescription = translator.translate(TRANSLATION_CONTEXT_PATH, "API error response", "(API error: ${ERROR_CODE}) ${DESCRIPTION}",
+ new AbstractMap.SimpleEntry<>("ERROR_CODE", apiErrorCode),
+ new AbstractMap.SimpleEntry<>("DESCRIPTION", responseDescription)
+ );
+ }
+
try {
// try to identify response type by status code
int responseCode = Integer.parseInt(response.responseCode());
@@ -164,6 +194,50 @@ public class ApiClient {
return result;
}
+
+ private String getApiErrorCode(Extension[] extensions) {
+ if(extensions == null)
+ return null;
+
+ for(Extension extension : extensions) {
+ if(extension.name() != null && !extension.name().isEmpty())
+ continue;
+
+ for(ExtensionProperty prop : extension.properties()) {
+ if(Constants.API_ERROR_CODE_EXTENSION_NAME.equals(prop.name())) {
+ return prop.value();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private String getContextPath(Extension[] extensions) {
+ return getTranslationExtensionValue(extensions, Constants.TRANSLATION_PATH_EXTENSION_NAME);
+ }
+
+ private String getDescriptionTranslationKey(Extension[] extensions) {
+ return getTranslationExtensionValue(extensions, Constants.TRANSLATION_ANNOTATION_DESCRIPTION_KEY);
+ }
+
+ private String getTranslationExtensionValue(Extension[] extensions, String key) {
+ if(extensions == null)
+ return null;
+
+ for(Extension extension : extensions) {
+ if(!Constants.TRANSLATION_EXTENSION_NAME.equals(extension.name()))
+ continue;
+
+ for(ExtensionProperty prop : extension.properties()) {
+ if(key.equals(prop.name())) {
+ return prop.value();
+ }
+ }
+ }
+
+ return null;
+ }
private String getHelpPatternForPath(String path) {
path = path
@@ -205,7 +279,7 @@ public class ApiClient {
match = COMMAND_PATTERN.matcher(command);
if(!match.matches())
- return this.translator.translate(Locale.getDefault(), "ApiClient: INVALID_COMMAND", "Invalid command! \nType help to get a list of commands.");
+ return this.translator.translate(TRANSLATION_CONTEXT_PATH, "invalid command", "Invalid command! \nType 'help all' to get a list of commands.");
// send the command to the API service
String method = match.group("method");
@@ -225,11 +299,22 @@ public class ApiClient {
if(status >= 400) {
result.append("HTTP Status ");
result.append(status);
- if(!StringUtils.isBlank(body)) {
- result.append(": ");
- result.append(body);
+ if(StringUtils.isBlank(body)) {
+ result.append(
+ this.translator.translate(TRANSLATION_CONTEXT_PATH, "error without body", "HTTP Status ${STATUS}",
+ new AbstractMap.SimpleEntry<>("STATUS", status)
+ )
+ );
+ }else{
+ result.append(
+ this.translator.translate(TRANSLATION_CONTEXT_PATH, "error with body", "HTTP Status ${STATUS}: ${BODY}",
+ new AbstractMap.SimpleEntry<>("STATUS", status),
+ new AbstractMap.SimpleEntry<>("BODY", body)
+ )
+ );
}
- result.append("\nType help to get a list of commands.");
+ result.append("\n");
+ result.append(this.translator.translate(TRANSLATION_CONTEXT_PATH, "error footer", "Type 'help all' to get a list of commands."));
} else {
result.append(body);
}
@@ -240,13 +325,17 @@ public class ApiClient {
builder.append(help.fullPath + "\n");
builder.append(" " + help.description + "\n");
if(help.success != null && help.success.size() > 0) {
- builder.append(" On success returns:\n");
+ builder.append(" ");
+ builder.append(this.translator.translate(TRANSLATION_CONTEXT_PATH, "help: success responses", "On success returns:"));
+ builder.append("\n");
for(String content : help.success) {
builder.append(" " + content + "\n");
}
}
if(help.errors != null && help.errors.size() > 0) {
- builder.append(" On failure returns:\n");
+ builder.append(" ");
+ builder.append(this.translator.translate(TRANSLATION_CONTEXT_PATH, "help: failure responses", "On failure returns:"));
+ builder.append("\n");
for(String content : help.errors) {
builder.append(" " + content + "\n");
}
diff --git a/src/api/ApiError.java b/src/api/ApiError.java
index 471fa9f2..d1e8b69d 100644
--- a/src/api/ApiError.java
+++ b/src/api/ApiError.java
@@ -117,4 +117,5 @@ public enum ApiError {
int getStatus() {
return this.status;
}
+
}
\ No newline at end of file
diff --git a/src/api/ApiErrorFactory.java b/src/api/ApiErrorFactory.java
index 784a3476..07b88e04 100644
--- a/src/api/ApiErrorFactory.java
+++ b/src/api/ApiErrorFactory.java
@@ -101,16 +101,16 @@ public class ApiErrorFactory {
// this.errorMessages.put(ApiError.NAME_FOR_SALE, createErrorMessageEntry(ApiError.NAME_FOR_SALE, NameResult.NAME_FOR_SALE.getStatusMessage()));
// this.errorMessages.put(ApiError.NAME_WITH_SPACE, createErrorMessageEntry(ApiError.NAME_WITH_SPACE, NameResult.NAME_WITH_SPACE.getStatusMessage()));
//AT
- this.errorMessages.put(ApiError.INVALID_CREATION_BYTES, createErrorMessageEntry(ApiError.INVALID_CREATION_BYTES, "error in creation bytes"));
// TODO
// this.errorMessages.put(ApiError.INVALID_DESC_LENGTH, createErrorMessageEntry(ApiError.INVALID_DESC_LENGTH,
// "invalid description length. max length ${MAX_LENGTH}",
// new AbstractMap.SimpleEntry("MAX_LENGTH", AT_Constants.DESC_MAX_LENGTH));
this.errorMessages.put(ApiError.EMPTY_CODE, createErrorMessageEntry(ApiError.EMPTY_CODE, "code is empty"));
this.errorMessages.put(ApiError.DATA_SIZE, createErrorMessageEntry(ApiError.DATA_SIZE, "invalid data length"));
+ this.errorMessages.put(ApiError.NULL_PAGES, createErrorMessageEntry(ApiError.NULL_PAGES, "invalid pages"));
this.errorMessages.put(ApiError.INVALID_TYPE_LENGTH, createErrorMessageEntry(ApiError.INVALID_TYPE_LENGTH, "invalid type length"));
this.errorMessages.put(ApiError.INVALID_TAGS_LENGTH, createErrorMessageEntry(ApiError.INVALID_TAGS_LENGTH, "invalid tags length"));
- this.errorMessages.put(ApiError.NULL_PAGES, createErrorMessageEntry(ApiError.NULL_PAGES, "invalid pages"));
+ this.errorMessages.put(ApiError.INVALID_CREATION_BYTES, createErrorMessageEntry(ApiError.INVALID_CREATION_BYTES, "error in creation bytes"));
//BLOG
this.errorMessages.put(ApiError.BODY_EMPTY, createErrorMessageEntry(ApiError.BODY_EMPTY, "invalid body it must not be empty"));
@@ -166,8 +166,7 @@ public class ApiErrorFactory {
}
public ApiException createError(ApiError error, Throwable throwable) {
- Locale locale = Locale.ENGLISH; // default locale
- return createError(locale, error, throwable);
+ return createError(null, error, throwable);
}
public ApiException createError(Locale locale, ApiError error, Throwable throwable) {
diff --git a/src/api/BlocksResource.java b/src/api/BlocksResource.java
index b9110212..5367b999 100644
--- a/src/api/BlocksResource.java
+++ b/src/api/BlocksResource.java
@@ -44,23 +44,33 @@ public class BlocksResource {
@GET
@Operation(
- description = "Returns an array of the 50 last blocks generated by your accounts",
+ 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="description.key", value="description")
+ @ExtensionProperty(name="path", value="GET"),
+ @ExtensionProperty(name="description.key", value="operation:description")
}),
responses = {
@ApiResponse(
- description = "The blocks"
+ description = "the blocks",
//content = @Content(schema = @Schema(implementation = ???))
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
),
@ApiResponse(
responseCode = "422",
- description = "Error: 201 - Wallet does not exist",
- extensions = @Extension(name = "translation", properties = {
- @ExtensionProperty(name="description.key", value="ApiError/201")
- }),
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "wallet does not exist",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="201")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/201")
+ })
+ }
)
}
)
@@ -73,26 +83,59 @@ public class BlocksResource {
@GET
@Path("/address/{address}")
@Operation(
- description = "Returns an array of the 50 last blocks generated by a specific address in your wallet",
+ description = "returns an array of the 50 last blocks generated by a specific address in your wallet",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET address:address"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The blocks"
- //content = @Content(schema = @Schema(implementation = ???))
+ description = "the blocks",
+ //content = @Content(schema = @Schema(implementation = ???)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
),
@ApiResponse(
responseCode = "400",
- description = "102 - Invalid address",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "invalid address",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="102")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/102")
+ })
+ }
),
@ApiResponse(
responseCode = "422",
- description = "201 - Wallet does not exist",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "wallet does not exist",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="201")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/201")
+ })
+ }
),
@ApiResponse(
responseCode = "422",
- description = "202 - Address does not exist in wallet",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "address does not exist in wallet",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="202")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/202")
+ })
+ }
)
}
)
@@ -105,21 +148,46 @@ public class BlocksResource {
@GET
@Path("/{signature}")
@Operation(
- description = "Returns the block that matches the given signature",
+ description = "returns the block that matches the given signature",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET signature"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The block"
- //content = @Content(schema = @Schema(implementation = ???))
+ description = "the block",
+ //content = @Content(schema = @Schema(implementation = ???)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
),
@ApiResponse(
responseCode = "400",
- description = "101 - Invalid signature",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "invalid signature",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="101")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/101")
+ })
+ }
),
@ApiResponse(
responseCode = "422",
- description = "301 - Block does not exist",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "block does not exist",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="301")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/301")
+ })
+ }
)
}
)
@@ -132,11 +200,20 @@ public class BlocksResource {
@GET
@Path("/first")
@Operation(
- description = "Returns the genesis block",
+ description = "returns the genesis block",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET first"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The block"
- //content = @Content(schema = @Schema(implementation = ???))
+ description = "the block",
+ //content = @Content(schema = @Schema(implementation = ???)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
)
}
)
@@ -149,11 +226,20 @@ public class BlocksResource {
@GET
@Path("/last")
@Operation(
- description = "Returns the last valid block",
+ description = "returns the last valid block",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET last"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The block"
- //content = @Content(schema = @Schema(implementation = ???))
+ description = "the block",
+ //content = @Content(schema = @Schema(implementation = ???)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
)
}
)
@@ -166,21 +252,46 @@ public class BlocksResource {
@GET
@Path("/child/{signature}")
@Operation(
- description = "Returns the child block of the block that matches the given signature",
+ description = "returns the child block of the block that matches the given signature",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET child:signature"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The block"
- //content = @Content(schema = @Schema(implementation = ???))
+ description = "the block",
+ //content = @Content(schema = @Schema(implementation = ???)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
),
@ApiResponse(
responseCode = "400",
- description = "101 - Invalid signature",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "invalid signature",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="101")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/101")
+ })
+ }
),
@ApiResponse(
responseCode = "422",
- description = "301 - Block does not exist",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "block does not exist",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="301")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/301")
+ })
+ }
)
}
)
@@ -193,11 +304,20 @@ public class BlocksResource {
@GET
@Path("/generatingbalance")
@Operation(
- description = "Calculates the generating balance of the block that will follow the last block",
+ description = "calculates the generating balance of the block that will follow the last block",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET generatingbalance"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The generating balance",
- content = @Content(schema = @Schema(implementation = long.class))
+ description = "the generating balance",
+ content = @Content(schema = @Schema(implementation = long.class)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
)
}
)
@@ -210,21 +330,46 @@ public class BlocksResource {
@GET
@Path("/generatingbalance/{signature}")
@Operation(
- description = "Calculates the generating balance of the block that will follow the block that matches the signature",
+ description = "calculates the generating balance of the block that will follow the block that matches the signature",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET generatingbalance:signature"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The block",
- content = @Content(schema = @Schema(implementation = long.class))
+ description = "the block",
+ content = @Content(schema = @Schema(implementation = long.class)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
),
@ApiResponse(
responseCode = "400",
- description = "101 - Invalid signature",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "invalid signature",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="101")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/101")
+ })
+ }
),
@ApiResponse(
responseCode = "422",
- description = "301 - Block does not exist",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "block does not exist",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="301")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/301")
+ })
+ }
)
}
)
@@ -237,11 +382,20 @@ public class BlocksResource {
@GET
@Path("/time")
@Operation(
- description = "Calculates the time it should take for the network to generate the next block",
+ description = "calculates the time it should take for the network to generate the next block",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET time"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The time", // in seconds?
- content = @Content(schema = @Schema(implementation = long.class))
+ description = "the time", // in seconds?
+ content = @Content(schema = @Schema(implementation = long.class)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
)
}
)
@@ -254,11 +408,20 @@ public class BlocksResource {
@GET
@Path("/time/{generatingbalance}")
@Operation(
- description = "Calculates the time it should take for the network to generate blocks when the current generating balance in the network is the specified generating balance",
+ description = "calculates the time it should take for the network to generate blocks when the current generating balance in the network is the specified generating balance",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET time:generatingbalance"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The time", // in seconds?
- content = @Content(schema = @Schema(implementation = long.class))
+ description = "the time", // in seconds?
+ content = @Content(schema = @Schema(implementation = long.class)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
)
}
)
@@ -271,11 +434,20 @@ public class BlocksResource {
@GET
@Path("/height")
@Operation(
- description = "Returns the block height of the last block.",
+ description = "returns the block height of the last block.",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET height"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The height",
- content = @Content(schema = @Schema(implementation = int.class))
+ description = "the height",
+ content = @Content(schema = @Schema(implementation = int.class)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
)
}
)
@@ -292,21 +464,46 @@ public class BlocksResource {
@GET
@Path("/height/{signature}")
@Operation(
- description = "Returns the block height of the block that matches the given signature",
+ description = "returns the block height of the block that matches the given signature",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET height:signature"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The height",
- content = @Content(schema = @Schema(implementation = int.class))
+ description = "the height",
+ content = @Content(schema = @Schema(implementation = int.class)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
),
@ApiResponse(
responseCode = "400",
- description = "101 - Invalid signature",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "invalid signature",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="101")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/101")
+ })
+ }
),
@ApiResponse(
responseCode = "422",
- description = "301 - Block does not exist",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "block does not exist",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="301")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/301")
+ })
+ }
)
}
)
@@ -319,16 +516,33 @@ public class BlocksResource {
@GET
@Path("/byheight/{height}")
@Operation(
- description = "Returns the block whith given height",
+ description = "returns the block whith given height",
+ extensions = @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="path", value="GET byheight:height"),
+ @ExtensionProperty(name="description.key", value="operation:description")
+ }),
responses = {
@ApiResponse(
- description = "The block"
- //content = @Content(schema = @Schema(implementation = ???))
+ description = "the block",
+ //content = @Content(schema = @Schema(implementation = ???)),
+ extensions = {
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="success_response:description")
+ })
+ }
),
@ApiResponse(
responseCode = "422",
- description = "301 - Block does not exist",
- content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
+ description = "block does not exist",
+ content = @Content(schema = @Schema(implementation = ApiErrorMessage.class)),
+ extensions = {
+ @Extension(properties = {
+ @ExtensionProperty(name="apiErrorCode", value="301")
+ }),
+ @Extension(name = "translation", properties = {
+ @ExtensionProperty(name="description.key", value="ApiError/301")
+ })
+ }
)
}
)
diff --git a/src/api/Constants.java b/src/api/Constants.java
new file mode 100644
index 00000000..b2d4708b
--- /dev/null
+++ b/src/api/Constants.java
@@ -0,0 +1,74 @@
+package api;
+
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.PathItem;
+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.List;
+
+class Constants {
+
+ public static final String TRANSLATION_EXTENSION_NAME = "translation";
+ public static final String TRANSLATION_PATH_EXTENSION_NAME = "path";
+
+ public static final String TRANSLATION_ANNOTATION_DESCRIPTION_KEY = "description.key";
+ public static final String TRANSLATION_ANNOTATION_SUMMARY_KEY = "summary.key";
+ public static final String TRANSLATION_ANNOTATION_TITLE_KEY = "title.key";
+ public static final String TRANSLATION_ANNOTATION_TERMS_OF_SERVICE_KEY = "termsOfService.key";
+
+ public static final String API_ERROR_CODE_EXTENSION_NAME = "apiErrorCode";
+
+
+ public static final List> TRANSLATABLE_INFO_PROPERTIES = asList(
+ new TranslatableProperty() {
+ @Override public String keyName() { return TRANSLATION_ANNOTATION_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 TRANSLATION_ANNOTATION_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 TRANSLATION_ANNOTATION_TERMS_OF_SERVICE_KEY; }
+ @Override public void setValue(Info item, String translation) { item.setTermsOfService(translation); }
+ @Override public String getValue(Info item) { return item.getTermsOfService(); }
+ }
+ );
+
+ public static final List> TRANSLATABLE_PATH_ITEM_PROPERTIES = asList(
+ new TranslatableProperty() {
+ @Override public String keyName() { return TRANSLATION_ANNOTATION_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 TRANSLATION_ANNOTATION_SUMMARY_KEY; }
+ @Override public void setValue(PathItem item, String translation) { item.setSummary(translation); }
+ @Override public String getValue(PathItem item) { return item.getSummary(); }
+ }
+ );
+
+ public static final List> TRANSLATABLE_OPERATION_PROPERTIES = asList(
+ new TranslatableProperty() {
+ @Override public String keyName() { return TRANSLATION_ANNOTATION_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 TRANSLATION_ANNOTATION_SUMMARY_KEY; }
+ @Override public void setValue(Operation item, String translation) { item.setSummary(translation); }
+ @Override public String getValue(Operation item) { return item.getSummary(); }
+ }
+ );
+
+ public static final List> TRANSLATABLE_API_RESPONSE_PROPERTIES = asList(
+ new TranslatableProperty() {
+ @Override public String keyName() { return TRANSLATION_ANNOTATION_DESCRIPTION_KEY; }
+ @Override public void setValue(ApiResponse item, String translation) { item.setDescription(translation); }
+ @Override public String getValue(ApiResponse item) { return item.getDescription(); }
+ }
+ );
+}
diff --git a/src/api/TranslatableProperty.java b/src/api/TranslatableProperty.java
new file mode 100644
index 00000000..8dd90e9a
--- /dev/null
+++ b/src/api/TranslatableProperty.java
@@ -0,0 +1,7 @@
+package api;
+
+interface TranslatableProperty {
+ public String keyName();
+ public void setValue(T item, String translation);
+ public String getValue(T item);
+}
diff --git a/src/globalization/ContextPaths.java b/src/globalization/ContextPaths.java
index e924b25d..8743d438 100644
--- a/src/globalization/ContextPaths.java
+++ b/src/globalization/ContextPaths.java
@@ -1,7 +1,6 @@
package globalization;
import java.nio.file.Paths;
-import javax.xml.stream.XMLStreamException;
public class ContextPaths {
@@ -18,6 +17,8 @@ public class ContextPaths {
}
public static String combinePaths(String left, String right) {
+ left = (left != null) ? left : "";
+ right = (right != null) ? right : "";
return Paths.get("/", left, right).normalize().toString();
}
diff --git a/src/globalization/TranslationXmlStreamReader.java b/src/globalization/TranslationXmlStreamReader.java
index db4f76be..790df7f3 100644
--- a/src/globalization/TranslationXmlStreamReader.java
+++ b/src/globalization/TranslationXmlStreamReader.java
@@ -18,6 +18,7 @@ import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.*;
+import org.apache.commons.text.StringEscapeUtils;
public class TranslationXmlStreamReader {
@@ -183,7 +184,7 @@ public class TranslationXmlStreamReader {
path = ContextPaths.combinePaths(state.path, value);
break;
case TRANSLATION_TEMPLATE_ATTRIBUTE_NAME:
- template = value;
+ template = unescape(value);
break;
default:
throw new javax.xml.stream.XMLStreamException("Unexpected attribute: " + name);
@@ -211,6 +212,10 @@ public class TranslationXmlStreamReader {
result.add(new TranslationEntry(state.locale, path, template));
}
+ private String unescape(String value) {
+ return StringEscapeUtils.unescapeJava(value);
+ }
+
private void assureIsValidPathExtension(String value) throws XMLStreamException {
if(ContextPaths.containsParentReference(value))
throw new javax.xml.stream.XMLStreamException("Parent reference .. is not allowed");
diff --git a/src/globalization/Translator.java b/src/globalization/Translator.java
index 91d4bb6b..d3d73747 100644
--- a/src/globalization/Translator.java
+++ b/src/globalization/Translator.java
@@ -94,32 +94,61 @@ public class Translator {
return translate(locale, contextPath, templateKey, map);
}
+ public String translate(String contextPath, String templateKey, AbstractMap.Entry... templateValues) {
+ Map map = createMap(templateValues);
+ return translate(contextPath, templateKey, map);
+ }
+
public String translate(Locale locale, String contextPath, String templateKey, Map templateValues) {
return translate(locale, contextPath, templateKey, null, templateValues);
}
+ public String translate(String contextPath, String templateKey, Map templateValues) {
+ return translate(contextPath, templateKey, null, templateValues);
+ }
+
public String translate(Locale locale, String contextPath, String templateKey, String defaultTemplate, AbstractMap.Entry... templateValues) {
Map map = createMap(templateValues);
return translate(locale, contextPath, templateKey, defaultTemplate, map);
}
+ public String translate(String contextPath, String templateKey, String defaultTemplate, AbstractMap.Entry... templateValues) {
+ Map map = createMap(templateValues);
+ return translate(contextPath, templateKey, defaultTemplate, map);
+ }
+
public String translate(Locale locale, String contextPath, String templateKey, String defaultTemplate, Map templateValues) {
// look for requested language
- String template = getTemplateFromNearestPath(locale, contextPath, templateKey);
+ String template = null;
+ if(locale != null)
+ template = getTemplateFromNearestPath(locale, contextPath, templateKey);
- if(template == null) {
- // scan default languages
- for(String language : this.settings().translationsDefaultLocales()) {
- Locale defaultLocale = Locale.forLanguageTag(language);
- template = getTemplateFromNearestPath(defaultLocale, contextPath, templateKey);
- if(template != null)
- break;
- }
+ if(template != null)
+ return substitute(template, templateValues);
+
+ return translate(contextPath, templateKey, defaultTemplate, templateValues);
+ }
+
+ public String translate(String contextPath, String templateKey, String defaultTemplate, Map templateValues) {
+ // scan default languages
+ String template = null;
+ for(String language : this.settings().translationsDefaultLocales()) {
+ Locale defaultLocale = Locale.forLanguageTag(language);
+ template = getTemplateFromNearestPath(defaultLocale, contextPath, templateKey);
+ if(template != null)
+ break;
}
if(template == null)
template = defaultTemplate; // fallback template
+
+ return substitute(template, templateValues);
+ }
+ private String substitute(String template, Map templateValues) {
+ if(templateValues == null)
+ return template;
+
StringSubstitutor sub = new StringSubstitutor(templateValues);
String result = sub.replace(template);