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; }