angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Refinando el multilenguaje: añadiendo la opción de volver a utilizar el idioma como una opción

Cuando se desactiva la función de reserva de idioma, el elemento no se muestra si la traducción no está disponible.

11 septiembre 2019
post main image
unsplash.com/@miabaker

En un post anterior describí la primera versión de la base de datos multilingüe utilizada en este sitio web. Por cada tabla que tenga campos que deban traducirse, añadimos una tabla'traducción' con estos campos. También he implementado la función de emergencia de idioma: si un elemento, como una entrada de blog, no existe en el idioma seleccionado, se muestra el elemento del idioma predeterminado (para todo el sistema). Esto funciona bien, pero ahora quiero añadir un complemento opcional que permita que algunos mensajes aparezcan sólo en español, otros sólo en francés, etc.

Podríamos implementar esto fácilmente cambiando el lenguaje alternativo a un lenguaje desconocido / inexistente. Ahora bien, si añadimos un mensaje en español, el mensaje en otros idiomas no está disponible, sólo lo que queremos. El único problema es que ahora ya no tenemos un lenguaje alternativo. ¿Es esto un problema?

Considere el siguiente caso. Tenemos una entrada en español, y queremos que la entrada inexistente en francés vuelva al idioma por defecto, el inglés. Pero no queremos que el mensaje inexistente en alemán vuelva al idioma por defecto. Esto no es posible con la implementación actual. ¿Con qué frecuencia ocurrirá tal condición? No muy a menudo, pero me imagino que puede suceder, y quiero prepararme para esto. Entonces, ¿cómo lo implementamos?

Algunas opciones de emergencia

Hay muchas opciones posibles para una base de datos multilingüe. Siempre necesitamos en algún lugar un interruptor que diga:

  • volver al elemento de idioma predeterminado si no hay traducción para el idioma seleccionado
  • no retroceder, lo que significa que este elemento no está disponible en el idioma seleccionado

Para el fallback podemos tener opciones como:

  • nivel récord de retroceso, por ejemplo, título, descripción
  • fallback a nivel de campo, por ejemplo, título

También podemos tener:

  • una sola opción, por ejemplo, sólo hay un idioma alternativo
  • recursivo, por ejemplo, si no existe en portugués, en español, si no existe en español, en inglés.

El lenguaje alternativo puede ser:

  • sistema completo
  • a un nivel récord
  • sobre el terreno

Sin entrar en muchos detalles se puede imaginar que implementando la máxima flexibilidad, todas las opciones, vamos a crear un sistema muy complejo que requerirá consultas complejas, de hecho, puede ser necesario construir un preprocesador complejo para el front-end para evitar que las páginas se vean lentas debido a todo el procesamiento de opciones de idioma.

Tomar decisiones

De vuelta a lo básico. Quiero para este sitio web:

  • Soporte para múltiples idiomas
  • Hay un idioma por defecto en todo el sistema
  • Hay un lenguaje alternativo en todo el sistema
  • La acción predeterminada debe ser la opción de retroceso de idioma
  • Por cada elemento de idioma debe ser posible desactivar la función de reserva de idioma, haciendo que el elemento no esté disponible si no se publica.

He definido los siguientes idiomas:

  • language_selected: idioma en uso
  • language_default: el idioma por defecto, una configuración estática en todo el sistema
  • language_fallback: el lenguaje alternativo, una configuración dinámica para todo el sistema basada en el idioma seleccionado

La mayoría de las veces el idioma de reserva será el idioma por defecto, pero en el futuro también podremos cambiarlo a otro idioma:

Example#1: fr-Be fallback to fr-FR

  • idioma por defecto: en-US
  • idioma seleccionado: fr-BE
  • idioma alternativo: fr-FR

Example#2: es-ES con retroceso a en-GB

  • idioma por defecto: en-GB
  • idioma seleccionado: es-ES
  • idioma alternativo: en-GB

Implementación

Quiero que las consultas no ralenticen el sistema, pero si lo hacen siempre podemos añadir 'query result caching' y/o preprocesamiento de consultas.

En este momento hay dos tablas de contenido:

  • ContenidoItem
  • ContenidoItemTraducción

Siempre empezamos seleccionando uno o más registros de ContentItem. Si el registro ContentItemTranslation no está presente (o no está publicado) para el idioma seleccionado, debemos retroceder, pero sólo si la retroalimentación está habilitada para este elemento. Tiempo para ver la consulta utilizada para recuperar los elementos. Distinguimos dos posibilidades:

  • ContentItemRegistro de traducción no existe
  • ContentItemRegistro de traducción existe

En ambos casos podemos hacer un join of union, la primera parte obtiene el registro para el idioma seleccionado y la segunda parte obtiene el idioma para el idioma por defecto. La dificultad es cómo desactivamos la función de emergencia. Cuando el registro ContentItemTranslation existe, podemos poner una bandera 'do_not_fallback' aquí, pero ¿qué pasa si el registro ContentItemTranslation no existe? Entonces necesitamos otra bandera específica de otro idioma en alguna parte y de nuevo esta bandera puede o no existir.

Una forma de hacerlo es añadir otra tabla ContentItemTranslationDoNotFallback que contenga sólo una bandera = do_not_fallback. Al igual que con ContentItemTranslation, los registros de ContentItemTranslationDoNotFallback pueden o no estar presentes. Entonces tenemos tres mesas:

  • ContenidoItem
  • ContenidoItemTraducción
  • ContenidoItemTraducciónDoNotFallback

Deseamos que la acción predeterminada sea"fallback to the fallback language" si el"registro de traducción no existe". Todo esto parece bastante sencillo. Pero esto también introduce un registro adicional de una nueva tabla que puede o no estar presente.

Otra forma de conseguir lo que queremos es añadir la bandera 'do_not_fallback' al registro de traducción. Quiero decir, si tenemos que añadir un registro a una nueva tabla ContentItemTranslationDoNotFallback para indicar'do_not_fallback', ¿por qué no usar el registro de la tabla ContentItemTranslation para este propósito? Si no existe, lo añadimos, de lo contrario simplemente lo usamos.

En este caso, la acción predeterminada sigue siendo la de retroceder al idioma de retroceso si el registro `traducción' no existe. Insertamos un registro de traducción ahora, no sólo cuando tenemos valor de traducción, sino también cuando queremos una copia de seguridad de este registro. En el registro de traducción utilizamos la bandera publicada / activa para indicar si la traducción existe. La siguiente tabla muestra las acciones para el idioma seleccionado.

Tabla
ContentItemTranslation
ContentItemTraducción
publicada
ContenidoItemTraducción
do_not_fallback
Acción
1 No existe - - Sin valor convertido, reserva
2 Existe Verdadero Verdadero o falso Utilizar el valor convertido
3

Existe

Falso Falso Sin valor convertido, reserva
4 Existe Falso Verdadero Sin valor convertido, sin retroceso, artículo no disponible

SQL interrogar

A continuación se muestra una posible implementación de la SQL consulta. Nótese que estoy usando SELECT's y UNION aquí que permiten una fácil selección de objetos cuando se usa una SQLAlchemy consulta. En la implementación actual tengo dos indicadores, borrado y estado, que deben ser False. content_item_parent_id = 0 significa el registro de traducción principal.

SET @language_fallback_id = 1;
SET @language_selected_id = 2;

### [1] = fallback: translation record does not exist

(
SELECT 
  ci.id ci_id, cit.id cit_id, cit.title cit_title
FROM content_item ci, content_item_translation cit
WHERE 
  ci.content_item_type = 1
  AND ci.content_item_parent_id = 0
  AND ci.published = 1
  AND cit.content_item_id = ci.id
  AND cit.content_item_parent_id = 0
  AND cit.published = 1
  AND cit.language_id = @language_fallback_id
  AND NOT EXISTS
    (
    # return 1 when exists
    SELECT 1 
    FROM content_item_translation cit2
    WHERE 
          cit2.content_item_id = ci.id
      AND cit2.content_item_parent_id = 0
      AND cit2.language_id = @language_selected_id
      )
)

UNION ALL

### [2] = use translation: translation record exists and published = True

(
SELECT 
  ci.id ci_id, cit.id cit_id, cit.title cit_title
FROM content_item ci, content_item_translation cit
WHERE 
  ci.content_item_type = 1
  AND ci.content_item_parent_id = 0
  AND ci.published = 1
  AND cit.content_item_id = ci.id
  AND cit.content_item_parent_id = 0
  AND cit.published = 1
  AND cit.language_id = @language_selected_id
)

UNION ALL

### [3] = fallback: translation record exists and published = False & do_not_fallback = False

(
SELECT 
  ci.id ci_id, cit.id cit_id, cit.title cit_title
FROM content_item ci, content_item_translation cit
WHERE 
  ci.content_item_type = 1
  AND ci.content_item_parent_id = 0
  AND ci.published = 1
  AND cit.content_item_id = ci.id
  AND cit.content_item_parent_id = 0
  AND cit.published = 1
  AND cit.language_id = @language_fallback_id
  AND EXISTS
    (
    # return 1 when published = False & do_not_fallback = False
    SELECT 1 
    FROM content_item ci2, content_item_translation cit2
    WHERE 
          ci2.id = ci.id
      AND ci2.content_item_type = 1
      AND ci2.content_item_parent_id = 0
      AND ci2.published = 1
      AND cit2.content_item_id = ci2.id
      AND cit2.content_item_parent_id = 0
      AND cit2.published = 0
      AND cit2.do_not_fallback = 0
      AND cit2.language_id = @language_selected_id
      )
)

ORDER BY ci_id DESC;

Por SQLAlchemy el momento decidí usar sql en bruto para obtener el conteo y los ids para la paginación y luego usar estos ids para seleccionar los objetos usando una segunda consulta. Sí, estoy un poco cansado de traducir SQL a SQLAlchemy todo el tiempo, toma mucho tiempo y ¿para qué? Aún así, creo que el uso ORM puede ayudar mucho a reducir el código, pero no todo el tiempo. Así que se trata de usarlo de la manera correcta.

Administrador

Por supuesto, el administrador debe poder ver el estado de los elementos de contenido. El estado del elemento de contenido de la traducción es por idioma, como se indica a continuación:

Publicado

No publicado, fallback

No publicado, no hay reserva, no disponible

Resumen

Hemos añadido la opción 'do_not_fallback' a los elementos de contenido de nuestra base de datos. Esto introdujo más complejidad, pero la sobrecarga no parece realmente masiva. En comparación con la situación en la que siempre hemos tenido que recurrir al idioma por defecto, los cambios son los siguientes:

  • El recuento de los registros (ítems) ya no es el recuento del número de registros para el idioma por defecto.
  • Cuando vemos un ítem y cambiamos el idioma a un ítem que no tiene traducción, entonces debemos mostrar que el ítem no está disponible en el idioma seleccionado.
  • La función `más vistos' (entradas de blog), la función `búsqueda' (entradas de blog), etc. ahora también debe filtrar los elementos no disponibles.

De nuevo mucho trabajo pero siento que esta es la funcionalidad multilenguaje que puedo usar para el resto de este proyecto.

Enlaces / créditos

Fallback
https://www.i18next.com/principles/fallback

Language Fallback
http://sitecore-masters.com/en/language-fallback/

Multi Language i18n & Localization in Pimcore
https://pimcore.com/docs/5.x/Development_Documentation/Multi_Language_i18n/index.html

Leer más

Multilanguage

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.