forked from OHDSI/WebAPI
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementation of internationalization. Supported Lanauges: - English - Russian - Korean - Chinese
- Loading branch information
1 parent
6ab5c74
commit b72cbec
Showing
20 changed files
with
14,602 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# Atlas localization | ||
|
||
## Backend | ||
|
||
Localization files directory: src/main/resources/i18n | ||
|
||
Localization file naming convention: messages_{lang}.json, where {lang} is two-letters (ISO 639-1) language code. | ||
|
||
List of supported localizations is presented in locales.json file in the same directory: | ||
``` | ||
[ | ||
{ | ||
"code": "en", | ||
"name": "English", | ||
"default": true | ||
}, | ||
{ | ||
"code": "ru", | ||
"name": "Русский" | ||
}, | ||
{ | ||
"code": "ko", | ||
"name": "한국어" | ||
}, | ||
{ | ||
"code": "zh", | ||
"name": "中文" | ||
} | ||
] | ||
``` | ||
|
||
Messages file is in JSON format and contains localized messages organized as a tree: | ||
``` | ||
{ | ||
"section1": { | ||
"subsection1": { | ||
"message": "Localized message1" | ||
}, | ||
"subsection2": { | ||
"message": "Localized message2" | ||
}, | ||
}, | ||
"section2": { | ||
"subsection1": { | ||
"message": "Localized message3 <%=param%>" | ||
} | ||
} | ||
} | ||
``` | ||
|
||
so each localized message has a unique path. For example: ``section1.subsection2.message`` points to "Localized message1". | ||
|
||
## Frontend | ||
|
||
Languages listed in locales.json are shown in the selector on the upper part of the page. | ||
|
||
When choosing a language, all text on the page changes dynamically without the need to reload the page. | ||
|
||
Reference to localized message can be placed whereever ko object is available. | ||
|
||
There are two methods in ko for localized messages (see js/extensions/bindings/i18nBinding.js): | ||
|
||
- `ko.i18n(path, defaultMessage)` where path is a path to the message in the localization file `messages_{lang}.json` and `defaultMessage` is a text, that will be used if there is no such path in localization file. | ||
|
||
- `ko.i18nformat(path, defaultMessage, parameters)` - same as above, but with parameters. | ||
For example: | ||
message (in localization file and/or default): `Localized message3 <%=param%>` | ||
parameters object: `{"param": "some text"}` | ||
result: `Localized message3 some text` | ||
Parameter values can be localized as well with any nesting level. | ||
|
||
Both methods return ko.pureComputed() function, that can be unwrapped or called to receive a localized message (see examples below). | ||
|
||
Examples: | ||
|
||
1. In HTML files through `data-bind="text: ..."` | ||
``` | ||
<span data-bind="text: ko.i18n('section1.subsection2.message', 'Localized message1')"></span> | ||
``` | ||
|
||
2. Passed as parameter, provided that it will be used as it is in data-bind attribute, or unwrapped in any other way: | ||
``` | ||
<atlas-modal params=" | ||
showModal: $component.isExecutionDesignShown, | ||
title: ko.i18n('components.analysisExecution.designModal.title', 'Design'), | ||
... | ||
``` | ||
|
||
3. In .js: | ||
``` | ||
alert( | ||
ko.i18n('cohortDefinitions.cohortDefinitionManager.confirms.save', | ||
'Cohort definition cannot be deleted because it is referenced in some analysis.')() | ||
) | ||
``` | ||
|
||
or | ||
``` | ||
alert( | ||
ko.unwrap( | ||
ko.i18n('cohortDefinitions.cohortDefinitionManager.confirms.save', | ||
'Cohort definition cannot be deleted because it is referenced in some analysis.') | ||
) | ||
) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package org.ohdsi.webapi; | ||
|
||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.web.servlet.LocaleResolver; | ||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | ||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; | ||
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; | ||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; | ||
|
||
import java.util.Locale; | ||
|
||
@Configuration | ||
public class I18nConfig { | ||
|
||
@Bean | ||
public LocaleResolver localeResolver() { | ||
|
||
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); | ||
localeResolver.setDefaultLocale(new Locale("en")); | ||
return localeResolver; | ||
} | ||
|
||
public void addInterceptors(InterceptorRegistry registry) { | ||
|
||
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); | ||
localeChangeInterceptor.setParamName("lang"); | ||
registry.addInterceptor(localeChangeInterceptor); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package org.ohdsi.webapi.i18n; | ||
|
||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import org.ohdsi.circe.helper.ResourceHelper; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Controller; | ||
|
||
import javax.annotation.PostConstruct; | ||
import javax.ws.rs.GET; | ||
import javax.ws.rs.Path; | ||
import javax.ws.rs.Produces; | ||
import javax.ws.rs.container.ContainerRequestContext; | ||
import javax.ws.rs.core.Context; | ||
import javax.ws.rs.core.MediaType; | ||
import javax.ws.rs.core.Response; | ||
import java.io.IOException; | ||
import java.net.URL; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.Objects; | ||
|
||
@Path("/i18n/") | ||
@Controller | ||
public class I18nController { | ||
|
||
@Value("${i18n.defaultLocale}") | ||
private String defaultLocale = "en"; | ||
|
||
@Autowired | ||
private I18nService i18nService; | ||
|
||
@GET | ||
@Path("/") | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public Response getResources(@Context ContainerRequestContext requestContext) { | ||
|
||
Locale locale = (Locale) requestContext.getProperty("language"); | ||
if (locale == null || !isLocaleSupported(locale.getLanguage())) { | ||
locale = Locale.forLanguageTag(defaultLocale); | ||
} | ||
String messages = i18nService.getLocaleResource(locale); | ||
return Response.ok(messages).build(); | ||
} | ||
|
||
private boolean isLocaleSupported(String code) { | ||
|
||
return i18nService.getAvailableLocales().stream().anyMatch(l -> Objects.equals(code, l.getCode())); | ||
} | ||
|
||
@GET | ||
@Path("/locales") | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public List<LocaleDTO> getAvailableLocales() { | ||
|
||
return i18nService.getAvailableLocales(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.ohdsi.webapi.i18n; | ||
|
||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
public interface I18nService { | ||
List<LocaleDTO> getAvailableLocales(); | ||
|
||
String translate(String key); | ||
String translate(String key, String defaultValue); | ||
|
||
String getLocaleResource(Locale locale); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package org.ohdsi.webapi.i18n; | ||
|
||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import org.ohdsi.circe.helper.ResourceHelper; | ||
import org.springframework.context.i18n.LocaleContextHolder; | ||
import org.springframework.stereotype.Component; | ||
|
||
import javax.annotation.PostConstruct; | ||
import javax.ws.rs.InternalServerErrorException; | ||
import java.io.IOException; | ||
import java.net.URL; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
@Component | ||
public class I18nServiceImpl implements I18nService { | ||
|
||
private List<LocaleDTO> availableLocales; | ||
|
||
@PostConstruct | ||
public void init() throws IOException { | ||
|
||
String json = ResourceHelper.GetResourceAsString("/i18n/locales.json"); | ||
ObjectMapper objectMapper = new ObjectMapper(); | ||
JavaType type = objectMapper.getTypeFactory().constructCollectionType(List.class, LocaleDTO.class); | ||
availableLocales = objectMapper.readValue(json, type); | ||
} | ||
|
||
@Override | ||
public List<LocaleDTO> getAvailableLocales() { | ||
|
||
return Collections.unmodifiableList(availableLocales); | ||
} | ||
|
||
@Override | ||
public String translate(String key) { | ||
|
||
return translate(key, key); | ||
} | ||
|
||
@Override | ||
public String translate(String key, String defaultValue) { | ||
|
||
try { | ||
Locale locale = LocaleContextHolder.getLocale(); | ||
String messages = getLocaleResource(locale); | ||
ObjectMapper mapper = new ObjectMapper(); | ||
JsonNode root = mapper.readTree(messages); | ||
String pointer = "/" + key.replaceAll("\\.", "/"); | ||
JsonNode node = root.at(pointer); | ||
return node.isValueNode() ? node.asText() : defaultValue; | ||
}catch (IOException e) { | ||
throw new InternalServerErrorException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public String getLocaleResource(Locale locale) { | ||
|
||
String resourcePath = String.format("/i18n/messages_%s.json", locale.getLanguage()); | ||
URL resourceURL = this.getClass().getResource(resourcePath); | ||
String messages = ""; | ||
if (resourceURL != null) { | ||
messages = ResourceHelper.GetResourceAsString(resourcePath); | ||
} | ||
return messages; | ||
} | ||
} |
Oops, something went wrong.