Dans Wao, j'ai la problématique de traduction d'entités. Les utilisateurs de Wao doivent pouvoir créer de nouvelles entités, et donc ajouter les traductions. Dans Wao, j'ai choisi de faire comme suit : - Création d'une entité Translation - id : le topiaid de l'entité traduite - locale : "fr", "en_GB" etc. - text : la trad - J'ai une interface TranslatableEntity (qui étend TopiaEntity) - Sur l'entité traductible, j'ajoute l'interface. Ça ajoute une ptite méthode pour obtenir le nom de l'entité dans la bonne langue An moment de restaurer les entités traductibles, le service passe par une méthode qui va charger toutes les traductions d'une entité donnée dans l'entité elle-même (oui, toutes les traductions et pas seulement la langue courante, car en contexte web, la traduction peut changer en live, sans recharger les données en base). J'ai pas la problématique, donc à voir si c'est adapté : si on veut pouvoir traduire les champs d'une entité, faut rajouter un champ "property" dans Translation. Pour l'instant je développe le truc dans Wao, pour voir où ça mène mais si l'approche s'avère judicieuse on pourra remonter ça dans Topia dans quelques temps : - en créant des stréréotypes « translatable » sur une entité ou sur un champ - ce stéréoptype ajoute dans le Abstract de l'entité une map pour l'entité ou une map pour chaque champ à traduire - on peut s'appuyer sur hibernate pour remplir cette map dès que l'entité est chargée depuis la base (en eager) -- Brendan Le Ny <bleny@codelutin.com> Code Lutin Conseil & Développement Logiciel Libre +33 (0)2 40 50 29 28 http://codelutin.com
Le Thu, 10 Feb 2011 17:46:45 +0100, Brendan Le Ny <bleny@codelutin.com> a écrit :
Dans Wao, j'ai la problématique de traduction d'entités.
Les utilisateurs de Wao doivent pouvoir créer de nouvelles entités, et donc ajouter les traductions. Dans Wao, j'ai choisi de faire comme suit : - Création d'une entité Translation - id : le topiaid de l'entité traduite - locale : "fr", "en_GB" etc. - text : la trad - J'ai une interface TranslatableEntity (qui étend TopiaEntity) - Sur l'entité traductible, j'ajoute l'interface. Ça ajoute une ptite méthode pour obtenir le nom de l'entité dans la bonne langue
An moment de restaurer les entités traductibles, le service passe par une méthode qui va charger toutes les traductions d'une entité donnée dans l'entité elle-même (oui, toutes les traductions et pas seulement la langue courante, car en contexte web, la traduction peut changer en live, sans recharger les données en base).
En gros tu passes deux requêtes ? une pour avoir l'entité et une autre pour la traduction. Je suis pas fan, je préfère avoir un string avec l'ensemble des traductions directement dedans du genre {fr_FR : oui, en_GB : yes}. Julien
Le 10/02/2011 20:05, Julien Ruchaud a écrit :
Le Thu, 10 Feb 2011 17:46:45 +0100, Brendan Le Ny <bleny@codelutin.com> a écrit :
Dans Wao, j'ai la problématique de traduction d'entités.
Les utilisateurs de Wao doivent pouvoir créer de nouvelles entités, et donc ajouter les traductions. Dans Wao, j'ai choisi de faire comme suit : - Création d'une entité Translation - id : le topiaid de l'entité traduite - locale : "fr", "en_GB" etc. - text : la trad - J'ai une interface TranslatableEntity (qui étend TopiaEntity) - Sur l'entité traductible, j'ajoute l'interface. Ça ajoute une ptite méthode pour obtenir le nom de l'entité dans la bonne langue
An moment de restaurer les entités traductibles, le service passe par une méthode qui va charger toutes les traductions d'une entité donnée dans l'entité elle-même (oui, toutes les traductions et pas seulement la langue courante, car en contexte web, la traduction peut changer en live, sans recharger les données en base). En gros tu passes deux requêtes ? une pour avoir l'entité et une autre pour la traduction. Je suis pas fan, je préfère avoir un string avec l'ensemble des traductions directement dedans du genre {fr_FR : oui, en_GB : yes}.
Julien : C'est parce que tu raisonnes Wikitty. Pour moi, ta plus grosse problématique là c'est que tu dois charger directement toutes les traductions pour switcher directement dans l'interface Web. Est-ce une vraie volonté ? Le fait d'avoir 2 requêtes ne me gêne pas car cela permet de séparer le support d'i18n. Exemple de succession d'instructions : 1. service.findBookByName("Les misérables", "en_GB") 2. \- Book foundBook = bookDao.findByName("Les misérables") 3. \- i18nTool.loadLang(foundBook, "en_GB") // remplace les valeurs dans les champs de book ?? 4. \- return foundBook; // ou une copie ou je ne sais quoi Le gros avantage que je vois, c'est que l'étape 3 est complètement optionelle et pourra donc être désactivée facilement (l'entité a donc une langue par défaut). Et c'est super important, car j'ai déjà vu des applications s'écrouler côté perf à cause du support i18n. Ensuite par rapport à ta proposition pour l'entité Translation, je pense qu'il te faut le champ "property". (en fait, sans ce champ je vois pas comment tu veux faire ?) Je pense que c'est important que ton entité Translation soit construite avec 4 champs : - topiaId - locale - property - value (les 3 premiers formant une clé composite) Pourquoi est-ce important: de cette manière, tu peux faire en sorte de charger en 1 seule requête l'ensemble des traductions dont tu as besoin pour une entité : tu peux à volonté je préciser qu'1, 2 ou 3 champ de la clé composite. Donc moi je pense que c'est une assez bonne idée : c'est ce que Yannick a mis en place chez son client là avec mon aide. Du coup, le mieux placé pour te faire un retour sur cette solution, c'est Yannick. Cette solution n'est bien sur pas parfaite, mais elle a le mérite d'être 'moins pire' à mon sens. Parceque je pense que les autres solutions sont soit plus complexes à mettre en oeuvre, soit trop gourmandes en perfs, soit trop intrusives... (Mais je ne demande qu'à être convaincu du contraire !) Il n'en reste pas moins que ça me semble difficilement conjuguable avec le fait que tu sois obligé de chargé directement toutes les langues :[ Arnaud. PS: Pour t'aider un peu, voici quelques extraits de code de ce qui a été mis en place par Yannick : @Entity public class LocalizedEntry implements Serializable { @EmbeddedId protected I18nKey id = new I18nKey(); protected String value; // Getter et setters normaux public I18nKey getId() { return this.id; } [...] // Getters et setters par délégation sur 'id' pour faciliter la manipulation public String getLocale() { return this.id.getLocale(); } [...] } @Embeddable public class I18nKey implements Serializable { protected String fqn; // A noter que le 'topiaId' fait "économiser" un champ protected Long entityPk; protected String field; protected String locale; // getters et setters normaux [...] } DAO: public Map<String, String> getTranslations(final Class<?> entityClass, final Long entityPk, final String locale) { final String fqn = entityClass.getName(); // build String query final StringBuilder builder = new StringBuilder("SELECT i18n.id.field, i18n.value "); builder.append(" FROM "); builder.append(LocalizedEntry.class.getSimpleName() + " i18n "); builder.append(" WHERE "); builder.append(" i18n.id.fqn = :fqn "); builder.append(" AND i18n.id.locale = :language "); builder.append(" AND i18n.id.entityPk = :entityPk "); final String ql = builder.toString(); // Create query final Query query = this.entityManager.createQuery(ql); query.setParameter("fqn", fqn); query.setParameter("language", locale); query.setParameter("entityPk", entityPk); final List<Object[]> queryResults = query.getResultList(); //Create a map <field, value> for each fields final Map<String, String> result = new LinkedHashMap(queryResults.size()); for (final Object[] line : queryResults) { result.put((String) line[0], (String) line[1]); } return result; }
Arnaud a tout dit j'ai l'impression. Niveau retour, je pense qu'il est trop tot pour les faire. Ce n'est en place que partiellement (en gros, là ou je l'ai fait pour voir la faisabilité), et tester à coup de petits TU. Le Chef de Produit ayant jugé le multilingue pas urgent vis à vis du retard des implem (meme s'il n'y a plus qu'à utiliser une strategy [complexité d emes données] deja prete, mais j'ai assez parler de mon camarade cette semaine). Mais ça me permet d'évoquer justement un point fort à mon gout que ventait arnaud: le multilingue reste optionnel! Et actuellement, tout tourne sur une langue par defaut! Il n'y aura qu'à brancher le multilingue si besoin. L'entité elle meme n'est pas dénaturée, et reste facilement appréhendable. A coté de ça, on me proposait deux autres solutions: - avoir une entité dont les champs "multilingues" sont des "clefs" (int), qui reference dans une autre entité servant le multilingue avec 3 champs: clef, langue, valeur. - avoir une entité dont les champs "multilingues" sont sous forme de liste ou de map, de sorte que l'entité contienne directement tous ces champs. Mais pour moi, wikitty est/a cette solution. Je ne suis pas sur qu'il y de bonnes ou de mauvaise solution, mais mon choix s'est porté sur la premiere parce qu'elle me parait plus simple, plus "propre" pour l'entité, et elle reste optionnelle. Je note de faire un retour lorsque ce sera mis en place et un minimum rodé, car je pensais etre le seul à avoir cette problematique mais ça semble etre une petite problematique assez general. -- Yannick <martel@codelutin.com> Société Code Lutin Tel: 02 40 50 29 28 http://www.codelutin.com
Hello, Moi je vois une gros inconvénients, c'est quand tu charges des collections à partir de getter. Comme cela ce passe dans votre solution ? Julien
Le 13/02/2011 11:41, Julien Ruchaud a écrit :
Moi je vois une gros inconvénients, c'est quand tu charges des collections à partir de getter. Comme cela ce passe dans votre solution ?
C'est pas seulement dans le cas des collections. Dès que tu ne charges pas l'entité traductible elle-même mais une entité associée dans le modèle, si tu fais un get (collection ou non), il faut passer par la traduction. Je suppose que si on ajoute la requête de traduction au mapping-hibernate pour l'exécuter systématiquement (non-lazy), quoiqu'il arrive tu récupères des entités traduites donc avec cette solution, ça devrait le faire. -- Brendan Le Ny <bleny@codelutin.com> Code Lutin Conseil & Développement Logiciel Libre +33 (0)2 40 50 29 28 http://codelutin.com
Le 13/02/2011 11:41, Julien Ruchaud a écrit :
Moi je vois une gros inconvénients, c'est quand tu charges des collections à partir de getter. Comme cela ce passe dans votre solution ?
Et bien il faut que tu charges les traductions de tes entités dans la collection. Pas le choix dans notre cas en fait puisqu'on ne peut pas remplacer JPA par Wikitty, et on ne peut pas non plus dénaturiser les entités... Et le sujet du thread à la base, c'est 'i18n dans Topia', donc est-ce qu'on cherche une solution pour ToPIA ou bien on considère qu'on ne fait pas ça dans ToPIA ? Arnaud.
Le 16/02/2011 09:16, Arnaud Thimel a écrit :
Et le sujet du thread à la base, c'est 'i18n dans Topia', donc est-ce qu'on cherche une solution pour ToPIA ou bien on considère qu'on ne fait pas ça dans ToPIA ?
Je m'auto-repond : à la base, le thread est là pour parler de i18n AVEC ToPIA et non DANS ToPIA. Brendan, par rapport à ton besoin, vas-tu rester sur ToPIA ? Arnaud.
Brendan, par rapport à ton besoin, vas-tu rester sur ToPIA ?
Heu la question est un peu bizarre, je vais pas migrer tout Wao sur Wikitty juste pour ça... Donc, oui je reste sur Topia. Effectivement, je me suis posé la question : topic user/devel ? Bon pour l'instant, l'objectif est de voir comment je peux utiliser topia pour gérer l'internationalisation de mes entités dans Wao. Après, si on arrive à trouver une solution générique, réutilisable, on pourra extraire tout ça est remonter le tout dans topia pour que ça profite à tous. À ce moment là, on en discutera plutôt sur devel. Donc pour l'instant, ma problématique c'est "i18n avec topia" et plus tard ça deviendra peut-être "i18n dans topia", désolé pour le titre ambigu. Toutefois, je préfère voir comment ça se passe dans Wao, plutôt que de me lancer dans Topia sans avoir vu les problèmes qui vont vraiment se poser. Dans Wao, la problématique est reportée, on veut faire d'autres choses avant que je me re-colle sur cette problématique d'i18n (pas avant plusieurs semaines). Ça nous laisse le temps de réfléchir. -- Brendan Le Ny Code Lutin
participants (4)
-
Arnaud Thimel -
Brendan Le Ny -
Julien Ruchaud -
Yannick Martel