Reputation: 63
I want to implement an API Rest that verifies the internationalized database and replaces it with the one found in the MESSAGES table according to the regional configuration. The product in the database:
Insert into PRODUCTS (ID, NAME, PRICE) VALUES (1, 'products.name.generatedproductcode', 100.00);
Where NAME is an i18n key of the MESSAGE table
In the MESSAGES table
Insert into MESSAGES (LOCALE,KEY_REFERENCE,VALUE) values ('es_ES','products.name.generatedproductcode','teléfono móvil');
Insert into MESSAGES (LOCALE,KEY_REFERENCE,VALUE) values ('en_GB','products.name.generatedproductcode','mobile phone');
I would like to have a situation where I have to duplicate code that I can mark with an annotation like in18n the field or using aspectj to replace the value with the language that exists in the Locale
@Entity
public class Product {
private Long id;
@CustomI18nResource
private String name;
private Double price;
}
There are some limitations for example the Locale would have it only in HTTP. I am currently replacing manually from the controller and the value (something expensive at the code level since there are several entities). I am thinking what I should do as a solution to send the KEY to the frontend and that the i18n be administered in the front. Has anyone faced this problem and how has it been resolved?
Solution:
Oliver's answer is just what i was trying to do thank you very much
I have written the solution and published it on Github here:
https://github.com/jestevez/springboot-i18n-database
Upvotes: 1
Views: 1680
Reputation: 83081
The abstraction that's probably useful here is MessageSource
. Within Spring Framework it's usually used to resolve messages against a resource bundle (a properties file) but you can easily implement a database backed variant, probably rather using JDBC to avoid overhead and heavily caching the values.
If you have that, you can use a MessageSourceAccessor
e.g. in your controller code to prepare a model to be rendered for the domain object at hand that would know which values to i18nize, lookup the translated value and put that into the model. The …Accessor
abstraction picks up the locale from whatever LocaleResolver
is configured. IIRC, in Spring Boot this is an AcceptHeaderLocaleResolver
, i.e. the value given in the Accept-Language
HTTP request header defines which language is chosen.
If you want to completely automate this you might want to plug into your serialization mechanism to do the translation transparently for you. Jackson allows the customization of serializers so that those could use the MessageSourceAccessor
to automatically translate all fields equipped with a certain annotation.
@Retention(RetentionPolicy.RUNTIME)
@interface JsonInternationalized {}
@RequiredArgsConstructor
class I18nModule extends SimpleModule {
private final MessageSource messageSource;
@Override
public void setupModule(SetupContext context) {
MessageSourceAccessor accessor = new MessageSourceAccessor(messageSource);
InternationalizedSerializer serializer = new InternationalizedSerializer(accessor);
context.addBeanSerializerModifier(new InternationalizingBeanSerializerModifier(serializer));
}
@RequiredArgsConstructor
static class InternationalizingBeanSerializerModifier extends BeanSerializerModifier {
private final InternationalizedSerializer serializer;
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
for (BeanPropertyWriter writer : beanProperties) {
if (writer.getAnnotation(JsonInternationalized.class) != null) {
writer.assignSerializer(serializer);
}
}
return beanProperties;
}
}
@RequiredArgsConstructor
static class InternationalizedSerializer extends ToStringSerializer {
private static final long serialVersionUID = -2391442803792997283L;
private final MessageSourceAccessor accessor;
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(accessor.getMessage(value.toString()));
}
}
}
Here are the important aspects:
I18nModule
registers a BeanSerializerModifier
that inspects the writers for bean properties annotated with said annotation and registers a custom serializer for those properties that will resolve the property via the MessageSource
handed into the module.In a Spring Boot application, you can get this module to work by simply regitering it as Spring bean in a configuration class:
@Bean
I18nModule i18nModule(MessageSource messageSource) {
return new I18nModule(messageSource);
}
As you can see this requires a bit of ceremony. I'm gonna take this back to the team to see whether we can improve the out of the box experience a bit.
Upvotes: 4