Aujourd'hui, nous parlerons de multilinguisme. Alors c'est quoi?
Le multilinguisme, autrement dit
l'internationalisation , fait partie du développement d'une application adaptable à plusieurs langues sans changer la logique du programme. Considérez la situation : vous créez une application Web pour une grande société commerciale destinée aux résidents d'un grand nombre de pays dans lesquels, par exemple, des langues telles que le russe, l'anglais et l'espagnol sont parlées. Vous devez le rendre pratique pour tous les utilisateurs. L'idée est la suivante : un lecteur russophone devrait pouvoir voir vos données en russe, un Américain sera plus à l'aise pour lire le matériel en anglais et un Espagnol - en espagnol (de façon inattendue, n'est-ce pas ?). Considérons
aujourd'hui
plusieurs modèles d'internationalisation . , et pour l'un d'eux (qui est celui que j'aime le plus :) Regardons l'implémentation en Java. En tant que cobaye, nous aurons aujourd'hui une plaque signalétique de film. Ne soyons pas trop pervers, nous n'aurons donc pas beaucoup d'intervenants. Par exemple, ça va. Et nous traduirons les noms des films (réalisateurs - pour les figurants) :
1. Tableau avec traduction pour chaque langue
L'essence de ce modèle : chaque langue possède une table distincte dans la base de données, qui contient toutes les cellules à traduire. L’inconvénient de cette méthode est que chaque fois que nous ajoutons une nouvelle langue, nous devons ajouter une nouvelle table. Autrement dit, imaginons que notre client se porte très bien et qu'il étend son application à de nombreux pays (en fait, langues) du monde. Cela signifie que vous devrez ajouter un comprimé par langue. De ce fait, nous aurons une base de données constituée à moitié ou presque entièrement de tables de traduction auxiliaires :
Schéma des films eux-mêmes : Tables de traduction :
2. Un pour tous
Dans chaque table appartenant à un modèle spécifique, un champ avec un identifiant pour la plaque de langue est ajouté. En conséquence, la base de données contient également ce tableau avec des traductions. Le problème est qu'un objet peut correspondre à plusieurs traductions (langues). En conséquence, il y aura une duplication des entités, ce qui confondra et compliquera grandement la logique, et ce n'est pas bon.
Nous regardons UML : Films de table : Langages de table :
3. Colonne par langue
Une colonne de traduction distincte est créée pour chaque colonne pour chaque langue du tableau. L'inconvénient de cette approche est que, encore une fois, si un grand nombre de langues sont ajoutées, la structure de la base de données devra être modifiée à chaque fois, ce qui est considéré comme une mauvaise approche. Imaginez aussi à quel point les panneaux réclamant l’internationalisation seront exagérés. Il peut être intéressant d'envisager un modèle dans lequel le nombre de langues prises en charge est connu à l'avance, il n'y en a pas beaucoup et chaque modèle doit exister dans toutes les variantes linguistiques.
UML : Tableau tout compris :
4. Traduction externe
Cette option est implémentée en connectant des outils externes (Google Translate, Bing Translate, etc.). Il est utilisé si vous avez besoin de fournir des informations au plus grand nombre de visiteurs possible, et ces informations sont nombreuses. Tant. Dans ce cas, vous pouvez décider de ne pas stocker directement les informations dans la base de données dans toutes les langues, mais de les traduire dynamiquement. Mais il ne faut pas oublier que la qualité de la traduction automatique laisse souvent à désirer. L'option ne peut être considérée que comme très économique (lorsqu'il n'y a pas de ressources pour traduire chaque publication). Un problème courant en termes de traduction correcte est que les traducteurs qui ne connaissent pas bien la langue choisissent le mauvais sens d'un mot et confondent l'utilisateur, l'obligeant à comprendre de manière indépendante le sens de ce qui est écrit sur le bouton. Il est également important non seulement de traduire correctement la phrase, mais également de lui donner un sens dans une langue et une nationalité spécifiques. Les développeurs ont beaucoup de problèmes avec les genres dans certaines langues. Ils doivent dupliquer la phrase dans le code en fonction du sexe de l'utilisateur et tenir également compte du fait que non seulement les noms ont un genre, mais que les adjectifs et les verbes sont également fléchis différemment. Il existe des cas où, après avoir sélectionné une langue autre que l'anglais dans l'application, avec les mots de la langue sélectionnée, il reste encore des éléments non traduits. C'est encore pire si plusieurs langues sont affichées et cela s'avère être une sorte de Babylone, où tout est mélangé et où l'utilisateur ne comprend pas l'application. Par exemple :
https://cloud.google.com/translate/
5. Fichiers de support au niveau de l'application
Des fichiers séparés sont créés pour stocker les traductions. Il peut s'agir d'une lime par langue ou d'une lime par langue et par comprimé (concassage fin). Cette option est souvent utilisée car de nombreux textes peuvent être stockés dans ces fichiers, ce qui signifie que les tables et la base de données elle-même ne seront pas surchargées. Un autre avantage est que vous n'avez pas besoin de cliquer sur la base de données pour ces champs, et les fichiers du code peuvent être remplacés dynamiquement en fonction de la langue demandée. De ce fait, le fichier nous sert de dictionnaire, dans lequel la clé est la langue, la valeur est le texte. Mais nous ne nous limitons pas au format « .properties » ci-dessous, et ces formats de fichiers peuvent varier considérablement : JSON, XML, etc. Les inconvénients sont que dans ce cas, la normalisation de la base de données est considérablement réduite. De plus, l’intégrité des données ne dépend plus uniquement de la base de données, mais également du mécanisme de sérialisation.
Excellent article sur ce sujet Exemple de fichiers de dictionnaire avec traductions :
6. Table de traduction auxiliaire pour chaque table
À mon avis, la solution la plus flexible. L'essence de cette approche est de créer un tableau séparé pour les langues. Lorsqu'il est nécessaire d'implémenter la possibilité de traductions pour la table en question, un lien est créé avec la table des langues, et la table des liens contient l'identifiant de la langue, l'identifiant de l'élément et les colonnes avec les traductions. Ce n'est pas aussi effrayant que ça en a l'air. Cette approche permet une extensibilité assez flexible des langages pris en charge. Regardons de plus près.
UML : Table avec films : Table des langues : Table des traductions : Et, comme je l'ai dit plus haut, regardons l'implémentation d'une des options dans le code Java (comme vous l'avez compris, ce sera la dernière option). Il n’y a rien de tel dans l’application elle-même : on va passer des contrôleurs aux couches dao. Nous examinerons la méthode create - pour un exemple, cela suffira. Alors c'est parti)) Notre essence est un film :
@Builder
@Getter
public class Movie {
private Long id;
private String producer;
}
Rien d'intéressant, juste implémenter le modèle du premier tableau.
Contrôleur avec convertisseurs dto (Data Transfer Object) :
@RestController
@RequiredArgsConstructor
@RequestMapping(path = "/cities")
public class MovieController {
private final MovieService movieService;
@PostMapping
public ResponseEntity<moviedto> create(MovieDTO movieDTO) {
return new ResponseEntity<>(toDTO(movieService.create(fromDTO(movieDTO), movieDTO.getNameTranslations()), movieDTO.getNameTranslations()), HttpStatus.CREATED);
}
private Movie fromDTO(MovieDTO dto) {
return Movie.builder()
.id(dto.getId())
.producer(dto.getProducer())
.build();
}
private MovieDTO toDTO(Movie movie, Map<string, string=""> nameTranslation) {
return MovieDTO.builder()
.id(movie.getId())
.producer(movie.getProducer())
.nameTranslations(nameTranslation)
.build();
}
}
Dans DTO, nous transmettons les traductions sous forme de cartes, la clé est l'abréviation de la langue, la valeur est la valeur de la traduction (nom du film).
DTO :
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MovieDTO {
@JsonProperty("id")
private Long id;
@JsonProperty("name")
private String producer;
@JsonProperty("nameTranslations")
private Map<String, String> nameTranslations;
}
Ici, nous voyons la classe dto elle-même, comme écrit ci-dessus, une carte pour les traductions, les champs restants sont un affichage du modèle Movie.
Passons au service movie :
public interface MovieService {
Movie create(Movie movie, Map nameList);
}
Sa mise en œuvre :
@Service
@RequiredArgsConstructor
public class MovieServiceImpl implements MovieService {
private final MovieDAO movieDAO;
private LanguageService languageService;
@Override
public Movie create(Movie movie, Map<string, string=""> nameList) {
movieDAO.create(movie);
Map<Long, String> map = new HashMap<>();
nameList.forEach((x, y) -> map.put(languageService.getIdByLangCode(x), y));
movieDAO.createTranslator(movie.getId(), map);
return movie;
}
}
Nous voyons ici l'utilisation d'un service LanguageService relativement tiers pour récupérer l'identifiant de langue par son abréviation. Et avec cet identifiant, nous sauvegardons nos traductions (également sous forme de carte) dans la table de connexion.
Regardons le DAO :
public interface MovieDAO {
void create(Movie movie);
void createTranslator(Long movieId, Map<Long,String> nameTranslations);
}
Mise en œuvre:
@RequiredArgsConstructor
@Repository
public class MovieDAOImpl implements MovieDAO {
private final JdbcTemplate jdbcTemplate;
private static final String CREATE_MOVIE = "INSERT INTO movies(id, producer) VALUES(?, ?)";
private static final String CREATE_TRANSLATOR = "INSERT INTO movies_translator(movies_id, language_id, name) VALUES(?, ?, ?)";
@Override
public void create(Movie movie) {
jdbcTemplate.update(CREATE_MOVIE, movie.getId(), movie.getProducer());
}
@Override
public void createTranslator(Long movieId, Map<Long, String> nameTranslations) {
nameTranslations.forEach((x, y) -> jdbcTemplate.update(CREATE_TRANSLATOR, movieId, x, y));
}
}
Ici, nous voyons la préservation de l'essence et des langues (dictionnaire). Et oui, Spring JDBC est utilisé ici : je le considère préférable pour les débutants, car il est plus transparent. Passons à un service « tiers ».
Service linguistique :
public interface LanguageService {
Long getIdByLangCode(String lang);
}
Mise en œuvre:
@Service
@RequiredArgsConstructor
public class LanguageServiceImpl implements LanguageService {
private final LanguageDAO languageDAO;
@Override
public Long getIdByLangCode(String lang) {
return languageDAO.getIdByLangCode(lang);
}
}
Rien de spécial, recherchez par nom abrégé.
DAO :
public interface LanguageDAO {
Long getIdByLangCode(String lang);
}
Mise en œuvre:
@RequiredArgsConstructor
@Repository
public class LanguageDAOImpl implements LanguageDAO {
private final JdbcTemplate jdbcTemplate;
private static final String FIND_ID_BY_LANG_CODE = "SELECT id FROM languages WHERE lang_code = ?";
@Override
public Long getIdByLangCode(String lang) {
return jdbcTemplate.queryForObject(FIND_ID_BY_LANG_CODE, Long.class, lang);
}
}
Structure : Tous les modèles décrits ci-dessus ont droit à la vie. Vous devez décider lequel utiliser en fonction de la situation. Bien sûr, ce n’est pas tout : il existe de nombreuses autres approches différentes, notamment l’utilisation de différentes bases de données pour différents langages, l’utilisation de caches, différents frameworks, etc. C'est tout pour moi aujourd'hui et...
GO TO FULL VERSION