Sitio web de mejores prácticas en varios idiomas

179

He estado luchando con esta pregunta durante algunos meses, pero no he estado en una situación en la que necesite explorar todas las opciones posibles antes. En este momento, siento que es hora de conocer las posibilidades y crear mi propia preferencia personal para usar en mis próximos proyectos.

Permítanme primero esbozar la situación que estoy buscando.

Estoy a punto de actualizar / volver a desarrollar un sistema de gestión de contenido que he estado usando durante bastante tiempo. Sin embargo, siento que el lenguaje múltiple es una gran mejora para este sistema. Antes no usaba ningún framework, pero voy a usar Laraval4 para el próximo proyecto. Laravel parece la mejor opción de una forma más limpia de codificar PHP. Sidenote: Laraval4 should be no factor in your answer. Estoy buscando formas generales de traducción que sean independientes de la plataforma / marco.

Lo que debe traducirse

Como el sistema que busco debe ser lo más fácil de usar posible, el método de gestión de la traducción debe estar dentro del CMS. No debería ser necesario iniciar una conexión FTP para modificar archivos de traducción o cualquier plantilla analizada html / php.

Además, estoy buscando la forma más fácil de traducir varias tablas de bases de datos, quizás sin la necesidad de crear tablas adicionales.

¿Qué se me ocurrió?

Como ya he estado buscando, leyendo y probando cosas yo mismo. Hay un par de opciones que tengo. Pero todavía no siento que haya alcanzado un método de mejores prácticas para lo que realmente estoy buscando. En este momento, esto es lo que se me ocurrió, pero este método también tiene efectos secundarios.

  1. Plantillas analizadas en PHP: PHP debe analizar el sistema de plantillas. De esta manera, puedo insertar los parámetros traducidos en el HTML sin tener que abrir las plantillas y modificarlas. Además de eso, las plantillas analizadas de PHP me dan la posibilidad de tener 1 plantilla para el sitio web completo en lugar de tener una subcarpeta para cada idioma (que he tenido antes). El método para alcanzar este objetivo puede ser Smarty, TemplatePower, Laravel's Blade o cualquier otro analizador de plantillas. Como dije, esto debería ser independiente de la solución escrita.
  2. Basada en la base de datos : tal vez no necesito mencionar esto nuevamente. Pero la solución debe ser impulsada por la base de datos. El CMS está destinado a ser orientado a objetos y MVC, por lo que necesitaría pensar en una estructura de datos lógica para las cadenas. Como mis plantillas se estructurarán: Plantillas / Controlador / view.php quizá Esta estructura tendría más sentido: Controller.View.parameter. La tabla de la base de datos tendría estos campos largos con un valuecampo. Dentro de las plantillas podríamos usar algún método de clasificación como echo __('Controller.View.welcome', array('name', 'Joshua'))y el parámetro contiene Welcome, :name. Así el resultado es Welcome, Joshua. Esta parece una buena manera de hacerlo, porque los parámetros como: nombre son fáciles de entender por el editor.
  3. Carga baja de la base de datos : por supuesto, el sistema anterior provocaría cargas de carga de la base de datos si estas cadenas se cargan sobre la marcha. Por lo tanto, necesitaría un sistema de almacenamiento en caché que vuelva a procesar los archivos de idioma tan pronto como se editen / guarden en el entorno de administración. Debido a que se generan archivos, también se necesita un buen diseño del sistema de archivos. Creo que podemos ir con languages/en_EN/Controller/View.phpo .ini, lo que más te convenga. Quizás un .ini incluso se analice más rápido al final. Esto debe contener los datos en el format parameter=value; . Supongo que esta es la mejor manera de hacerlo, ya que cada vista que se representa puede incluir su propio archivo de idioma si existe. Los parámetros de idioma deben cargarse en una vista específica y no en un ámbito global para evitar que los parámetros se sobrescriban entre sí.
  4. Traducción de la tabla de base de datos : de hecho, esto es lo que más me preocupa. Estoy buscando una manera de crear traducciones de Noticias / Páginas / etc. lo más rápido posible. Tener dos tablas para cada módulo (por ejemplo Newsy News_translations) es una opción, pero se siente mucho trabajo para obtener un buen sistema. Una de las cosas que se me ocurrió se basa en un data versioningsistema que escribí: hay un nombre de tabla de base de datos Translations, esta tabla tiene una combinación única de language, tablenameyprimarykey. Por ejemplo: en_En / News / 1 (Refiriéndose a la versión en inglés del elemento News con ID = 1). Pero hay dos grandes desventajas en este método: en primer lugar, esta tabla tiende a ser bastante larga con una gran cantidad de datos en la base de datos y, en segundo lugar, sería un trabajo tremendo usar esta configuración para buscar en la tabla. Por ejemplo, buscar la babosa SEO del elemento sería una búsqueda de texto completo, lo cual es bastante tonto. Pero por otro lado: es una forma rápida de crear contenido traducible en cada tabla muy rápido, pero no creo que este profesional sobrepase los contras.
  5. Trabajo front-end : también el front-end necesitaría algo de reflexión. Por supuesto, almacenaríamos los idiomas disponibles en una base de datos y (des) activaría los que necesitamos. De esta manera, el script puede generar un menú desplegable para seleccionar un idioma y el back-end puede decidir automáticamente qué traducciones se pueden hacer usando el CMS. El idioma elegido (por ejemplo, en_EN) se usaría al obtener el archivo de idioma para una vista o para obtener la traducción correcta para un elemento de contenido en el sitio web.

Entonces, ahí están. Mis ideas hasta ahora. Ni siquiera incluyen opciones de localización para fechas, etc. todavía, pero como mi servidor admite PHP5.3.2 +, la mejor opción es usar la extensión intl como se explica aquí: http://devzone.zend.com/1500/internationalization-in -php-53 / - pero esto sería útil en cualquier estadio posterior de desarrollo. Por ahora, el problema principal es cómo tener las mejores prácticas de traducción del contenido en un sitio web.

Además de todo lo que expliqué aquí, todavía tengo otra cosa que aún no he decidido, parece una pregunta simple, pero de hecho me ha estado dando dolores de cabeza:

Traducción de URL? ¿Deberíamos hacer esto o no? y de que manera

Entonces ... si tengo esta url: http://www.domain.com/about-usy el inglés es mi idioma predeterminado. ¿Debería traducirse esta URL http://www.domain.com/over-onscuando elijo holandés como mi idioma? ¿O deberíamos ir por el camino fácil y simplemente cambiar el contenido de la página visible en /about. Lo último no parece una opción válida porque eso generaría múltiples versiones de la misma URL, esta indexación del contenido fallará de la manera correcta.

Otra opción está usando en su http://www.domain.com/nl/about-uslugar. Esto genera al menos una URL única para cada contenido. Además, sería más fácil ir a otro idioma, por ejemplo, http://www.domain.com/en/about-usy la URL proporcionada es más fácil de entender tanto para los visitantes de Google como para los humanos. Con esta opción, ¿qué hacemos con los idiomas predeterminados? ¿Debería el idioma predeterminado eliminar el idioma seleccionado por defecto? Entonces, redirigir http://www.domain.com/en/about-usa http://www.domain.com/about-us... En mi opinión, esta es la mejor solución, porque cuando el CMS está configurado para un solo idioma, no es necesario tener esta identificación de idioma en la URL.

Y una tercera opción es una combinación de ambas opciones: usar el "idioma-identificación-menos" -URL ( http://www.domain.com/about-us) para el idioma principal. Y use una URL con una babosa SEO traducida para sublenguajes: http://www.domain.com/nl/over-ons&http://www.domain.com/de/uber-uns

Espero que mi pregunta haga que se te quiebren las cabezas, ¡seguro que me hicieron la mía! Ya me ayudó a resolver las cosas como una pregunta aquí. Me dio la posibilidad de revisar los métodos que he usado antes y la idea que tengo para mi próximo CMS.

¡Me gustaría agradecerle por tomarse el tiempo de leer este montón de texto!

// Edit #1:

Olvidé mencionar: la función __ () es un alias para traducir una cadena dada. Dentro de este método, obviamente, debería haber algún tipo de método alternativo donde el texto predeterminado se carga cuando aún no hay traducciones disponibles. Si falta la traducción, debe insertarse o el archivo de traducción debe regenerarse.

Joshua - Pendo
fuente
3
¿Qué pasa con gettext? php.net/manual/en/book.gettext.php
Francois Bourgeois

Respuestas:

115

Premisa del tema

Hay tres aspectos distintos en un sitio multilingüe:

  • traducción de interfaz
  • contenido
  • enrutamiento de URL

Si bien todos se interconectaron de diferentes maneras, desde el punto de vista del CMS se administran utilizando diferentes elementos de la IU y se almacenan de manera diferente. Parece que tiene confianza en su implementación y comprensión de los dos primeros. La pregunta era sobre el último aspecto: "¿Traducción de URL? ¿Deberíamos hacer esto o no? ¿Y de qué manera?"

¿De qué puede estar hecha la URL?

Una cosa muy importante es que no se entusiasme con IDN . En cambio, favorezca la transliteración (también: transcripción y romanización). Si bien a primera vista IDN parece una opción viable para las URL internacionales, en realidad no funciona como se anuncia por dos razones:

  • algunos navegadores convertirán los caracteres no ASCII como 'ч'o 'ž'en '%D1%87'y'%C5%BE'
  • si el usuario tiene temas personalizados, es muy probable que la fuente del tema no tenga símbolos para esas letras

De hecho, intenté acercarme a IDN hace unos años en un proyecto basado en Yii (horrible framework, en mi humilde opinión). Encontré los dos problemas mencionados antes de desechar esa solución. Además, sospecho que podría ser un vector de ataque.

Opciones disponibles ... tal como las veo.

Básicamente tiene dos opciones, que podrían resumirse como:

  • http://site.tld/[:query]: donde [:query]determina la elección de idioma y contenido

  • http://site.tld/[:language]/[:query]: donde [:language]parte de la URL define la elección del idioma y [:query]se usa solo para identificar el contenido

La consulta es Α y Ω ..

Digamos que eliges http://site.tld/[:query].

En ese caso, tiene una fuente principal de lenguaje: el contenido del [:query]segmento; y dos fuentes adicionales:

  • valor $_COOKIE['lang']para ese navegador en particular
  • lista de idiomas en el encabezado HTTP Accept-Language (1) , (2)

Primero, debe hacer coincidir la consulta con uno de los patrones de enrutamiento definidos (si su elección es Laravel, lea aquí ). En una coincidencia exitosa de patrón, entonces necesita encontrar el idioma.

Tendría que pasar por todos los segmentos del patrón. Encuentre las posibles traducciones para todos esos segmentos y determine qué idioma se utilizó. Las dos fuentes adicionales (cookie y encabezado) se usarían para resolver conflictos de enrutamiento, cuando (no "si") surgen.

Tomemos, por ejemplo: http://site.tld/blog/novinka.

Eso es transliteración de "блог, новинка", que en inglés significa aproximadamente "blog", "latest".

Como ya puede notar, en ruso "блог" será transcrito como "blog". Lo que significa que para la primera parte de [:query]usted (en el mejor de los casos ) terminará con una ['en', 'ru']lista de idiomas posibles. Luego tomas el siguiente segmento: "novinka". Eso podría tener solamente un idioma en la lista de posibilidades: ['ru'].

Cuando la lista tiene un elemento, ha encontrado correctamente el idioma.

Pero si termina con 2 (ejemplo: ruso y ucraniano) o más posibilidades ... o 0 posibilidades, según sea el caso. Tendrá que usar cookies y / o encabezado para encontrar la opción correcta.

Y si todo lo demás falla, elige el idioma predeterminado del sitio.

Idioma como parámetro

La alternativa es usar URL, que se puede definir como http://site.tld/[:language]/[:query]. En este caso, al traducir una consulta, no necesita adivinar el idioma, porque en ese momento ya sabe cuál usar.

También hay una fuente secundaria de lenguaje: el valor de la cookie. Pero aquí no tiene sentido meterse con el encabezado Accept-Language, porque no está tratando con una cantidad desconocida de idiomas posibles en caso de "arranque en frío" (cuando el usuario abre el sitio por primera vez con una consulta personalizada).

En cambio, tiene 3 opciones simples y priorizadas:

  1. si el [:language]segmento está configurado, úselo
  2. si $_COOKIE['lang']está configurado, úsalo
  3. usar idioma predeterminado

Cuando tenga el idioma, simplemente intente traducir la consulta, y si la traducción falla, use el "valor predeterminado" para ese segmento en particular (según los resultados de la ruta).

¿No hay aquí una tercera opción?

Sí, técnicamente es posible combinar ambos enfoques, pero eso complicaría el proceso y sólo dar cabida a las personas que quieren URL manualmente el cambio de http://site.tld/en/newsa http://site.tld/de/newsy esperar que la página de noticias al cambio al Alemán.

Pero incluso este caso probablemente podría mitigarse utilizando el valor de la cookie (que contendría información sobre la elección previa de idioma), para implementar con menos magia y esperanza.

¿Qué enfoque usar?

Como ya habrás adivinado, recomendaría http://site.tld/[:language]/[:query]como la opción más sensata.

También en una situación de palabra real, tendría una tercera parte importante en la URL: "título". Como en el nombre del producto en la tienda en línea o título del artículo en el sitio de noticias.

Ejemplo: http://site.tld/en/news/article/121415/EU-as-global-reserve-currency

En este caso '/news/article/121415'sería la consulta, y 'EU-as-global-reserve-currency'es el título. Puramente para fines de SEO.

¿Se puede hacer en Laravel?

Un poco, pero no por defecto.

No estoy muy familiarizado con eso, pero por lo que he visto, Laravel utiliza un mecanismo de enrutamiento basado en patrones simples. Para implementar URL multilingües, probablemente tendrá que ampliar las clases principales , ya que el enrutamiento multilingüe necesita acceso a diferentes formas de almacenamiento (base de datos, caché y / o archivos de configuración).

Está enrutado. ¿Ahora que?

Como resultado de todo, terminaría con dos valiosas piezas de información: idioma actual y segmentos de consulta traducidos. Estos valores se pueden usar para enviarlos a la (s) clase (s) que producirán el resultado.

Básicamente, la siguiente URL: http://site.tld/ru/blog/novinka(o la versión sin '/ru') se convierte en algo así como

$parameters = [
   'language' => 'ru',
   'classname' => 'blog',
   'method' => 'latest',
];

Que solo se usa para despachar:

$instance = new {$parameter['classname']};
$instance->{'get'.$parameters['method']}( $parameters );

.. o alguna variación, dependiendo de la implementación particular.

tereško
fuente
1
Gracias por otra idea más! Muy atento! Estaba pensando en tener el parámetro de idioma en la URL también. Esto simplemente parece la mejor manera de identificar un idioma en particular, no solo para el usuario sino también para fines de SEO. En caso de que un usuario cambie / es / news a / de / news, mi idea era hacer una redirección 301 (permanente) a / de / nachrichten, por ejemplo. Solo para asegurarse de que cada idioma tenga solo una URL única por página (nuevamente para fines de SEO)
Joshua - Pendo
Cada vez es más difícil seleccionar la mejor respuesta, actualmente hay aproximadamente 3/4 respuestas que merecen al menos una parte de la recompensa cada una. Combinados se convierten en una respuesta sólida a todo lo que quería aclarar juntos :)
Joshua - Pendo
Acepté tu respuesta para darte al menos un representante adicional de la respuesta detallada que diste en la traducción de URL. ¡Altamente apreciado! Sin embargo, la recompensa es un premio para la persona que está debajo de ti, ya que respondió a todos los aspectos de mi pregunta de una manera independiente de la plataforma.
Joshua - Pendo
52

Implementando i18n sin el rendimiento alcanzado usando un preprocesador como lo sugiere Thomas Bley

En el trabajo, recientemente pasamos por la implementación de i18n en un par de nuestras propiedades, y una de las cosas con las que seguimos luchando fue el éxito en el desempeño de lidiar con la traducción sobre la marcha, luego descubrí esta gran publicación de blog de Thomas Bley que inspiró la forma en que usamos i18n para manejar grandes cargas de tráfico con problemas de rendimiento mínimos.

En lugar de llamar a funciones para cada operación de traducción, que como sabemos en PHP es costosa, definimos nuestros archivos base con marcadores de posición, luego usamos un preprocesador para almacenar en caché esos archivos (almacenamos el tiempo de modificación del archivo para asegurarnos de que estamos sirviendo el último contenido en todo momento).

Las etiquetas de traducción

Thomas usa {tr}y {/tr}etiquetas para definir dónde comienzan y terminan las traducciones. Debido al hecho de que estamos usando TWIG, no queremos usarlo {para evitar confusiones, así que usamos [%tr%]y en su [%/tr%]lugar. Básicamente, esto se ve así:

`return [%tr%]formatted_value[%/tr%];`

Tenga en cuenta que Thomas sugiere usar el inglés base en el archivo. No hacemos esto porque no queremos tener que modificar todos los archivos de traducción si cambiamos el valor en inglés.

Los archivos INI

Luego, creamos un archivo INI para cada idioma, en el formato placeholder = translated:

// lang/fr.ini
formatted_value = number_format($value * Model_Exchange::getEurRate(), 2, ',', ' ') . '€'

// lang/en_gb.ini
formatted_value = '£' . number_format($value * Model_Exchange::getStgRate())

// lang/en_us.ini
formatted_value = '$' . number_format($value)

Sería trivial permitir que un usuario los modifique dentro del CMS, solo obtenga los pares de claves con un preg_splitencendido \no =y haciendo que el CMS pueda escribir en los archivos INI.

El componente de preprocesador

Esencialmente, Thomas sugiere usar una función de 'compilador' justo a tiempo (aunque, en verdad, es un preprocesador) como esta para tomar sus archivos de traducción y crear archivos PHP estáticos en el disco. De esta manera, esencialmente almacenamos en caché nuestros archivos traducidos en lugar de llamar a una función de traducción para cada cadena en el archivo:

// This function was written by Thomas Bley, not by me
function translate($file) {
  $cache_file = 'cache/'.LANG.'_'.basename($file).'_'.filemtime($file).'.php';
  // (re)build translation?
  if (!file_exists($cache_file)) {
    $lang_file = 'lang/'.LANG.'.ini';
    $lang_file_php = 'cache/'.LANG.'_'.filemtime($lang_file).'.php';

    // convert .ini file into .php file
    if (!file_exists($lang_file_php)) {
      file_put_contents($lang_file_php, '<?php $strings='.
        var_export(parse_ini_file($lang_file), true).';', LOCK_EX);
    }
    // translate .php into localized .php file
    $tr = function($match) use (&$lang_file_php) {
      static $strings = null;
      if ($strings===null) require($lang_file_php);
      return isset($strings[ $match[1] ]) ? $strings[ $match[1] ] : $match[1];
    };
    // replace all {t}abc{/t} by tr()
    file_put_contents($cache_file, preg_replace_callback(
      '/\[%tr%\](.*?)\[%\/tr%\]/', $tr, file_get_contents($file)), LOCK_EX);
  }
  return $cache_file;
}

Nota: No verifiqué que la expresión regular funciona, no la copié del servidor de nuestra compañía, pero puede ver cómo funciona la operación.

Cómo llamarlo

De nuevo, este ejemplo es de Thomas Bley, no de mí:

// instead of
require("core/example.php");
echo (new example())->now();

// we write
define('LANG', 'en_us');
require(translate('core/example.php'));
echo (new example())->now();

Almacenamos el idioma en una cookie (o variable de sesión si no podemos obtener una cookie) y luego la recuperamos en cada solicitud. Puede combinar esto con un $_GETparámetro opcional para anular el idioma, pero no sugiero subdominio por idioma o página por idioma porque hará que sea más difícil ver qué páginas son populares y reducirá el valor de entrada enlaces ya que los tendrás más dispersos

¿Por qué usar este método?

Nos gusta este método de preprocesamiento por tres razones:

  1. La gran ganancia de rendimiento de no llamar a un montón de funciones para contenido que rara vez cambia (con este sistema, 100k visitantes en francés solo terminarán ejecutando el reemplazo de traducción una vez).
  2. No agrega ninguna carga a nuestra base de datos, ya que utiliza archivos planos simples y es una solución puramente PHP.
  3. La capacidad de usar expresiones PHP dentro de nuestras traducciones.

Obtener contenido traducido de la base de datos

Simplemente agregamos una columna para el contenido en nuestra base de datos llamada language, luego usamos un método de acceso para la LANGconstante que definimos anteriormente, para que nuestras llamadas SQL (usando ZF1, lamentablemente) se vean así:

$query = select()->from($this->_name)
                 ->where('language = ?', User::getLang())
                 ->where('id       = ?', $articleId)
                 ->limit(1);

Nuestros artículos tienen una clave principal compuesta idy, languagepor lo tanto, el artículo 54puede existir en todos los idiomas. Nuestro valor LANGpredeterminado es en_USsi no se especifica.

Traducción URL Slug

Combinaría dos cosas aquí, una es una función en su rutina de carga que acepta un $_GETparámetro para el idioma y anula la variable de la cookie, y otra es el enrutamiento que acepta múltiples babosas. Entonces puede hacer algo como esto en su enrutamiento:

"/wilkommen" => "/welcome/lang/de"
... etc ...

Estos podrían almacenarse en un archivo plano que podría escribirse fácilmente desde su panel de administración. JSON o XML pueden proporcionar una buena estructura para soportarlos.

Notas sobre algunas otras opciones

Traducción sobre la marcha basada en PHP

No veo que ofrezcan ninguna ventaja sobre las traducciones preprocesadas.

Traducciones basadas en front-end

Durante mucho tiempo he encontrado estos interesantes, pero hay algunas advertencias. Por ejemplo, debe poner a disposición del usuario la lista completa de frases en su sitio web que planea traducir, esto podría ser problemático si hay áreas del sitio que mantiene ocultas o no les ha permitido el acceso.

También debería suponer que todos sus usuarios están dispuestos y pueden usar Javascript en su sitio, pero según mis estadísticas, alrededor del 2.5% de nuestros usuarios se ejecutan sin él (o usan Noscript para bloquear el uso de nuestros sitios) .

Traducciones basadas en bases de datos

Las velocidades de conectividad de la base de datos de PHP no son nada del otro mundo, y esto se suma a la sobrecarga ya elevada de llamar a una función en cada frase para traducir. Los problemas de rendimiento y escalabilidad parecen abrumadores con este enfoque.

Glitch Desire
fuente
Veo que te he confundido con "Traducciones de front-end", lo que quise decir era una forma de analizar las cadenas traducidas en la pantalla. ¡Definitivamente no estoy buscando una manera de traducirlo del lado del cliente! Lo que quise decir fue la forma más fácil de cambiar idiomas en el front-end, pero eso es obviamente usando una cookie o una configuración de usuario :)
Joshua - Pendo
Ah, y por Database-Driven apuntaba más al método de gestión de todas las traducciones, por lo que mi solución ideal sería un back-end que escribe traducciones en una base de datos seguido de una función que genera el componente de preprocesamiento que genera el PHP expediente. Why?: simple .. No quiero molestarme con pequeños cambios en el texto, los usuarios deberían poder hacerlo ellos mismos sin usar un editor de código y / o un programa ftp :)
Joshua - Pendo
@PENDO Sé que no quiso decir traducciones frontales, ese fue un comentario poco velado hacia el usuario que sugirió marcos de traducción frontales utilizando JS. ;)
Glitch Desire
@PENDO Estoy de acuerdo, usaría el backend como usted sugirió, pero en lugar de una base de datos, usaría un archivo plano por razones de rendimiento. Por supuesto, la sugerencia principal aquí es la representación previa de las plantillas en el cambio por lo que podría sustituir a los .INIarchivos de base de datos con una tabla de 3 columnas con placeholder, replacement, language. Tecla compuesta placeholdery language. Luego tenga otro 2-col con tempfile(ruta a la plantilla) y modified(DATETIME).
Glitch Desire
1
@PENDO Gracias. He puesto 250 copias de seguridad y planeo otorgarlo a Teresko en 24 horas cuando el sitio me lo permita, ya que seleccionó ambas respuestas como correctas, y creo que una división representaría mejor sus intenciones.
Glitch Desire
15

Le sugiero que no invente una rueda y use gettext y la lista abreviada de idiomas ISO. ¿Has visto cómo se implementa i18n / l10n en marcos o CMS populares?

Usando gettext tendrás una herramienta poderosa donde muchos casos ya están implementados como formas plurales de números. En inglés solo tiene 2 opciones: singular y plural. Pero en ruso, por ejemplo, hay 3 formas y no es tan simple como en inglés.

Además, muchos traductores ya tienen experiencia para trabajar con gettext.

Echa un vistazo a CakePHP o Drupal . Ambos multilingües habilitados. CakePHP como ejemplo de localización de interfaz y Drupal como ejemplo de traducción de contenido.

Para l10n, usar la base de datos no es el caso en absoluto. Habrá toneladas de consultas. El enfoque estándar es obtener todos los datos de l10n en la memoria en una etapa temprana (o durante la primera llamada a la función i10n si prefiere la carga diferida). Puede leer desde el archivo .po o desde DB todos los datos a la vez. Y que solo lea las cadenas solicitadas de la matriz.

Si necesita implementar una herramienta en línea para traducir la interfaz, puede tener todos esos datos en la base de datos, pero aún así guardar todos los datos en el archivo para trabajar con ellos. Para reducir la cantidad de datos en la memoria, puede dividir todos los mensajes / cadenas traducidos en grupos y luego cargar solo los grupos que necesita si es posible.

Así que tienes toda la razón en tu # 3. Con una excepción: generalmente es un archivo grande, no un archivo por controlador, más o menos. Porque lo mejor para el rendimiento es abrir un archivo. Probablemente sepa que algunas aplicaciones web cargadas compilan todo el código PHP en un archivo para evitar operaciones de archivo cuando se llama a include / require.

Sobre las URL. Google sugiere indirectamente utilizar la traducción:

para indicar claramente el contenido en francés: http://example.ca/fr/vélo-de-montagne.html

También creo que debe redirigir al usuario al prefijo de idioma predeterminado, por ejemplo, http://examlpe.com/about-us redirigirá a http://examlpe.com/en/about-us Pero si su sitio usa solo un idioma, entonces no necesita prefijos en absoluto.

Echa un vistazo: http://www.audiomicro.com/trailer-hit-impact-psychodrama-sound-effects-836925 http://nl.audiomicro.com/aanhangwagen-hit-effect-psychodrama-geluidseffecten-836925 http: / /de.audiomicro.com/anhanger-hit-auswirkungen-psychodrama-sound-effekte-836925

Traducir contenido es una tarea más difícil. Creo que habrá algunas diferencias con los diferentes tipos de contenido, por ejemplo, artículos, elementos de menú, etc. Pero en el # 4 estás en el camino correcto. Echa un vistazo en Drupal para tener más ideas. Tiene un esquema de base de datos suficientemente claro y una interfaz lo suficientemente buena para traducir. Como si estuvieras creando un artículo y seleccionas el idioma. Y luego puedes traducirlo a otros idiomas.

Interfaz de traducción Drupal

Creo que no es un problema con los slugs de URL. Simplemente puede crear una tabla separada para las babosas y será la decisión correcta. También usando índices correctos no es problema consultar la tabla incluso con una gran cantidad de datos. Y no fue una búsqueda de texto completo, pero la coincidencia de cadenas si usará el tipo de datos varchar para slug y también puede tener un índice en ese campo.

PD: Perdón, mi inglés está lejos de ser perfecto.

Yaroslav
fuente
Gracias por el tiempo dedicado a responder a mi pregunta. ¡Tu inglés es lo suficientemente bueno para que yo lo entienda! ¡Ya te haré +1 por tus esfuerzos!
Joshua - Pendo
Yaroslav, una vez más, gracias por tu respuesta. Sin embargo, fui con otras 2 respuestas que eran un poco más completas y explicaba los métodos utilizados detrás del código en lugar de señalar que ya está allí.
Joshua - Pendo
2
No hay problema. De hecho, eso responde más completo e interesante de leer para mí también. Pero espero que tengas algo útil de mi respuesta también.
Yaroslav
12

Depende de cuánto contenido tenga su sitio web. Al principio, utilicé una base de datos como todas las demás personas aquí, pero puede llevar mucho tiempo escribir todos los trabajos de una base de datos. No digo que este sea un método ideal y especialmente si tiene mucho texto, pero si desea hacerlo rápido sin usar una base de datos, este método podría funcionar, sin embargo, no puede permitir que los usuarios ingresen datos que se usará como archivos de traducción. Pero si agrega las traducciones usted mismo, funcionará:

Digamos que tienes este texto:

Welcome!

Puede ingresar esto en una base de datos con traducciones, pero también puede hacer esto:

$welcome = array(
"English"=>"Welcome!",
"German"=>"Willkommen!",
"French"=>"Bienvenue!",
"Turkish"=>"Hoşgeldiniz!",
"Russian"=>"Добро пожаловать!",
"Dutch"=>"Welkom!",
"Swedish"=>"Välkommen!",
"Basque"=>"Ongietorri!",
"Spanish"=>"Bienvenito!"
"Welsh"=>"Croeso!");

Ahora, si su sitio web utiliza una cookie, tiene esto, por ejemplo:

$_COOKIE['language'];

Para hacerlo más fácil, transformemos en un código que se pueda usar fácilmente:

$language=$_COOKIE['language'];

Si su idioma de cookies es galés y tiene este código:

echo $welcome[$language];

El resultado de esto será:

Croeso!

Si necesita agregar muchas traducciones para su sitio web y una base de datos consume demasiado, el uso de una matriz puede ser una solución ideal.

usuario3749746
fuente
1
Esto no está cerca de la respuesta que estaba pidiendo. Además, en lugar de tener todos los idiomas disponibles en cada página, será mejor que cree archivos como los lang.en.phpque se incluirán y use el $lang['welcome']que se declara en cada archivo.
Joshua - Pendo
7

Le sugeriré que no dependa realmente de la base de datos para la traducción, podría ser una tarea realmente desordenada y podría ser un problema extremo en el caso de la codificación de datos.

Tuve un problema similar hace tiempo y escribí la siguiente clase para resolver mi problema

Objeto: Locale \ Locale

<?php

  namespace Locale;

  class Locale{

// Following array stolen from Zend Framework
public $country_to_locale = array(
    'AD' => 'ca_AD',
    'AE' => 'ar_AE',
    'AF' => 'fa_AF',
    'AG' => 'en_AG',
    'AI' => 'en_AI',
    'AL' => 'sq_AL',
    'AM' => 'hy_AM',
    'AN' => 'pap_AN',
    'AO' => 'pt_AO',
    'AQ' => 'und_AQ',
    'AR' => 'es_AR',
    'AS' => 'sm_AS',
    'AT' => 'de_AT',
    'AU' => 'en_AU',
    'AW' => 'nl_AW',
    'AX' => 'sv_AX',
    'AZ' => 'az_Latn_AZ',
    'BA' => 'bs_BA',
    'BB' => 'en_BB',
    'BD' => 'bn_BD',
    'BE' => 'nl_BE',
    'BF' => 'mos_BF',
    'BG' => 'bg_BG',
    'BH' => 'ar_BH',
    'BI' => 'rn_BI',
    'BJ' => 'fr_BJ',
    'BL' => 'fr_BL',
    'BM' => 'en_BM',
    'BN' => 'ms_BN',
    'BO' => 'es_BO',
    'BR' => 'pt_BR',
    'BS' => 'en_BS',
    'BT' => 'dz_BT',
    'BV' => 'und_BV',
    'BW' => 'en_BW',
    'BY' => 'be_BY',
    'BZ' => 'en_BZ',
    'CA' => 'en_CA',
    'CC' => 'ms_CC',
    'CD' => 'sw_CD',
    'CF' => 'fr_CF',
    'CG' => 'fr_CG',
    'CH' => 'de_CH',
    'CI' => 'fr_CI',
    'CK' => 'en_CK',
    'CL' => 'es_CL',
    'CM' => 'fr_CM',
    'CN' => 'zh_Hans_CN',
    'CO' => 'es_CO',
    'CR' => 'es_CR',
    'CU' => 'es_CU',
    'CV' => 'kea_CV',
    'CX' => 'en_CX',
    'CY' => 'el_CY',
    'CZ' => 'cs_CZ',
    'DE' => 'de_DE',
    'DJ' => 'aa_DJ',
    'DK' => 'da_DK',
    'DM' => 'en_DM',
    'DO' => 'es_DO',
    'DZ' => 'ar_DZ',
    'EC' => 'es_EC',
    'EE' => 'et_EE',
    'EG' => 'ar_EG',
    'EH' => 'ar_EH',
    'ER' => 'ti_ER',
    'ES' => 'es_ES',
    'ET' => 'en_ET',
    'FI' => 'fi_FI',
    'FJ' => 'hi_FJ',
    'FK' => 'en_FK',
    'FM' => 'chk_FM',
    'FO' => 'fo_FO',
    'FR' => 'fr_FR',
    'GA' => 'fr_GA',
    'GB' => 'en_GB',
    'GD' => 'en_GD',
    'GE' => 'ka_GE',
    'GF' => 'fr_GF',
    'GG' => 'en_GG',
    'GH' => 'ak_GH',
    'GI' => 'en_GI',
    'GL' => 'iu_GL',
    'GM' => 'en_GM',
    'GN' => 'fr_GN',
    'GP' => 'fr_GP',
    'GQ' => 'fan_GQ',
    'GR' => 'el_GR',
    'GS' => 'und_GS',
    'GT' => 'es_GT',
    'GU' => 'en_GU',
    'GW' => 'pt_GW',
    'GY' => 'en_GY',
    'HK' => 'zh_Hant_HK',
    'HM' => 'und_HM',
    'HN' => 'es_HN',
    'HR' => 'hr_HR',
    'HT' => 'ht_HT',
    'HU' => 'hu_HU',
    'ID' => 'id_ID',
    'IE' => 'en_IE',
    'IL' => 'he_IL',
    'IM' => 'en_IM',
    'IN' => 'hi_IN',
    'IO' => 'und_IO',
    'IQ' => 'ar_IQ',
    'IR' => 'fa_IR',
    'IS' => 'is_IS',
    'IT' => 'it_IT',
    'JE' => 'en_JE',
    'JM' => 'en_JM',
    'JO' => 'ar_JO',
    'JP' => 'ja_JP',
    'KE' => 'en_KE',
    'KG' => 'ky_Cyrl_KG',
    'KH' => 'km_KH',
    'KI' => 'en_KI',
    'KM' => 'ar_KM',
    'KN' => 'en_KN',
    'KP' => 'ko_KP',
    'KR' => 'ko_KR',
    'KW' => 'ar_KW',
    'KY' => 'en_KY',
    'KZ' => 'ru_KZ',
    'LA' => 'lo_LA',
    'LB' => 'ar_LB',
    'LC' => 'en_LC',
    'LI' => 'de_LI',
    'LK' => 'si_LK',
    'LR' => 'en_LR',
    'LS' => 'st_LS',
    'LT' => 'lt_LT',
    'LU' => 'fr_LU',
    'LV' => 'lv_LV',
    'LY' => 'ar_LY',
    'MA' => 'ar_MA',
    'MC' => 'fr_MC',
    'MD' => 'ro_MD',
    'ME' => 'sr_Latn_ME',
    'MF' => 'fr_MF',
    'MG' => 'mg_MG',
    'MH' => 'mh_MH',
    'MK' => 'mk_MK',
    'ML' => 'bm_ML',
    'MM' => 'my_MM',
    'MN' => 'mn_Cyrl_MN',
    'MO' => 'zh_Hant_MO',
    'MP' => 'en_MP',
    'MQ' => 'fr_MQ',
    'MR' => 'ar_MR',
    'MS' => 'en_MS',
    'MT' => 'mt_MT',
    'MU' => 'mfe_MU',
    'MV' => 'dv_MV',
    'MW' => 'ny_MW',
    'MX' => 'es_MX',
    'MY' => 'ms_MY',
    'MZ' => 'pt_MZ',
    'NA' => 'kj_NA',
    'NC' => 'fr_NC',
    'NE' => 'ha_Latn_NE',
    'NF' => 'en_NF',
    'NG' => 'en_NG',
    'NI' => 'es_NI',
    'NL' => 'nl_NL',
    'NO' => 'nb_NO',
    'NP' => 'ne_NP',
    'NR' => 'en_NR',
    'NU' => 'niu_NU',
    'NZ' => 'en_NZ',
    'OM' => 'ar_OM',
    'PA' => 'es_PA',
    'PE' => 'es_PE',
    'PF' => 'fr_PF',
    'PG' => 'tpi_PG',
    'PH' => 'fil_PH',
    'PK' => 'ur_PK',
    'PL' => 'pl_PL',
    'PM' => 'fr_PM',
    'PN' => 'en_PN',
    'PR' => 'es_PR',
    'PS' => 'ar_PS',
    'PT' => 'pt_PT',
    'PW' => 'pau_PW',
    'PY' => 'gn_PY',
    'QA' => 'ar_QA',
    'RE' => 'fr_RE',
    'RO' => 'ro_RO',
    'RS' => 'sr_Cyrl_RS',
    'RU' => 'ru_RU',
    'RW' => 'rw_RW',
    'SA' => 'ar_SA',
    'SB' => 'en_SB',
    'SC' => 'crs_SC',
    'SD' => 'ar_SD',
    'SE' => 'sv_SE',
    'SG' => 'en_SG',
    'SH' => 'en_SH',
    'SI' => 'sl_SI',
    'SJ' => 'nb_SJ',
    'SK' => 'sk_SK',
    'SL' => 'kri_SL',
    'SM' => 'it_SM',
    'SN' => 'fr_SN',
    'SO' => 'sw_SO',
    'SR' => 'srn_SR',
    'ST' => 'pt_ST',
    'SV' => 'es_SV',
    'SY' => 'ar_SY',
    'SZ' => 'en_SZ',
    'TC' => 'en_TC',
    'TD' => 'fr_TD',
    'TF' => 'und_TF',
    'TG' => 'fr_TG',
    'TH' => 'th_TH',
    'TJ' => 'tg_Cyrl_TJ',
    'TK' => 'tkl_TK',
    'TL' => 'pt_TL',
    'TM' => 'tk_TM',
    'TN' => 'ar_TN',
    'TO' => 'to_TO',
    'TR' => 'tr_TR',
    'TT' => 'en_TT',
    'TV' => 'tvl_TV',
    'TW' => 'zh_Hant_TW',
    'TZ' => 'sw_TZ',
    'UA' => 'uk_UA',
    'UG' => 'sw_UG',
    'UM' => 'en_UM',
    'US' => 'en_US',
    'UY' => 'es_UY',
    'UZ' => 'uz_Cyrl_UZ',
    'VA' => 'it_VA',
    'VC' => 'en_VC',
    'VE' => 'es_VE',
    'VG' => 'en_VG',
    'VI' => 'en_VI',
    'VN' => 'vn_VN',
    'VU' => 'bi_VU',
    'WF' => 'wls_WF',
    'WS' => 'sm_WS',
    'YE' => 'ar_YE',
    'YT' => 'swb_YT',
    'ZA' => 'en_ZA',
    'ZM' => 'en_ZM',
    'ZW' => 'sn_ZW'
);

/**
 * Store the transaltion for specific languages
 *
 * @var array
 */
protected $translation = array();

/**
 * Current locale
 *
 * @var string
 */
protected $locale;

/**
 * Default locale
 *
 * @var string
 */
protected $default_locale;

/**
 *
 * @var string
 */
protected $locale_dir;

/**
 * Construct.
 *
 *
 * @param string $locale_dir            
 */
public function __construct($locale_dir)
{
    $this->locale_dir = $locale_dir;
}

/**
 * Set the user define localte
 *
 * @param string $locale            
 */
public function setLocale($locale = null)
{
    $this->locale = $locale;

    return $this;
}

/**
 * Get the user define locale
 *
 * @return string
 */
public function getLocale()
{
    return $this->locale;
}

/**
 * Get the Default locale
 *
 * @return string
 */
public function getDefaultLocale()
{
    return $this->default_locale;
}

/**
 * Set the default locale
 *
 * @param string $locale            
 */
public function setDefaultLocale($locale)
{
    $this->default_locale = $locale;

    return $this;
}

/**
 * Determine if transltion exist or translation key exist
 *
 * @param string $locale            
 * @param string $key            
 * @return boolean
 */
public function hasTranslation($locale, $key = null)
{
    if (null == $key && isset($this->translation[$locale])) {
        return true;
    } elseif (isset($this->translation[$locale][$key])) {
        return true;
    }

    return false;
}

/**
 * Get the transltion for required locale or transtion for key
 *
 * @param string $locale            
 * @param string $key            
 * @return array
 */
public function getTranslation($locale, $key = null)
{
    if (null == $key && $this->hasTranslation($locale)) {
        return $this->translation[$locale];
    } elseif ($this->hasTranslation($locale, $key)) {
        return $this->translation[$locale][$key];
    }

    return array();
}

/**
 * Set the transtion for required locale
 *
 * @param string $locale
 *            Language code
 * @param string $trans
 *            translations array
 */
public function setTranslation($locale, $trans = array())
{
    $this->translation[$locale] = $trans;
}

/**
 * Remove transltions for required locale
 *
 * @param string $locale            
 */
public function removeTranslation($locale = null)
{
    if (null === $locale) {
        unset($this->translation);
    } else {
        unset($this->translation[$locale]);
    }
}

/**
 * Initialize locale
 *
 * @param string $locale            
 */
public function init($locale = null, $default_locale = null)
{
    // check if previously set locale exist or not
    $this->init_locale();
    if ($this->locale != null) {
        return;
    }

    if ($locale == null || (! preg_match('#^[a-z]+_[a-zA-Z_]+$#', $locale) && ! preg_match('#^[a-z]+_[a-zA-Z]+_[a-zA-Z_]+$#', $locale))) {
        $this->detectLocale();
    } else {
        $this->locale = $locale;
    }

    $this->init_locale();
}

/**
 * Attempt to autodetect locale
 *
 * @return void
 */
private function detectLocale()
{
    $locale = false;

    // GeoIP
    if (function_exists('geoip_country_code_by_name') && isset($_SERVER['REMOTE_ADDR'])) {

        $country = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);

        if ($country) {

            $locale = isset($this->country_to_locale[$country]) ? $this->country_to_locale[$country] : false;
        }
    }

    // Try detecting locale from browser headers
    if (! $locale) {

        if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {

            $languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);

            foreach ($languages as $lang) {

                $lang = str_replace('-', '_', trim($lang));

                if (strpos($lang, '_') === false) {

                    if (isset($this->country_to_locale[strtoupper($lang)])) {

                        $locale = $this->country_to_locale[strtoupper($lang)];
                    }
                } else {

                    $lang = explode('_', $lang);

                    if (count($lang) == 3) {
                        // language_Encoding_COUNTRY
                        $this->locale = strtolower($lang[0]) . ucfirst($lang[1]) . strtoupper($lang[2]);
                    } else {
                        // language_COUNTRY
                        $this->locale = strtolower($lang[0]) . strtoupper($lang[1]);
                    }

                    return;
                }
            }
        }
    }

    // Resort to default locale specified in config file
    if (! $locale) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Check if config for selected locale exists
 *
 * @return void
 */
private function init_locale()
{
    if (! file_exists(sprintf('%s/%s.php', $this->locale_dir, $this->locale))) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Load a Transtion into array
 *
 * @return void
 */
private function loadTranslation($locale = null, $force = false)
{
    if ($locale == null)
        $locale = $this->locale;

    if (! $this->hasTranslation($locale)) {
        $this->setTranslation($locale, include (sprintf('%s/%s.php', $this->locale_dir, $locale)));
    }
}

/**
 * Translate a key
 *
 * @param
 *            string Key to be translated
 * @param
 *            string optional arguments
 * @return string
 */
public function translate($key)
{
    $this->init();
    $this->loadTranslation($this->locale);

    if (! $this->hasTranslation($this->locale, $key)) {

        if ($this->locale !== $this->default_locale) {

            $this->loadTranslation($this->default_locale);

            if ($this->hasTranslation($this->default_locale, $key)) {

                $translation = $this->getTranslation($this->default_locale, $key);
            } else {
                // return key as it is or log error here
                return $key;
            }
        } else {
            return $key;
        }
    } else {
        $translation = $this->getTranslation($this->locale, $key);
    }
    // Replace arguments
    if (false !== strpos($translation, '{a:')) {
        $replace = array();
        $args = func_get_args();
        for ($i = 1, $max = count($args); $i < $max; $i ++) {
            $replace['{a:' . $i . '}'] = $args[$i];
        }
        // interpolate replacement values into the messsage then return
        return strtr($translation, $replace);
    }

    return $translation;
  }
}

Uso

 <?php
    ## /locale/en.php

    return array(
       'name' => 'Hello {a:1}'
       'name_full' => 'Hello {a:1} {a:2}'
   );

$locale = new Locale(__DIR__ . '/locale');
$locale->setLocale('en');// load en.php from locale dir
//want to work with auto detection comment $locale->setLocale('en');

echo $locale->translate('name', 'Foo');
echo $locale->translate('name', 'Foo', 'Bar');

Cómo funciona

{a:1}se reemplaza por el primer argumento pasado al método Locale::translate('key_name','arg1') {a:2}se reemplaza por el segundo argumento pasado al métodoLocale::translate('key_name','arg1','arg2')

Cómo funciona la detección

  • De manera predeterminada, si geoipestá instalado, devolverá el código de país geoip_country_code_by_namey si geoip no está instalado, el respaldo al HTTP_ACCEPT_LANGUAGEencabezado
Shushant
fuente
¿De qué manera sería desordenada una base de datos? ¿Por los posibles caracteres en diferentes idiomas? Hasta ahora, principalmente tengo sitios web en inglés, francés, holandés y alemán, por lo que no es un problema por ahora. Gracias por la respuesta, pero como es solo una parte de la respuesta, no ganará la recompensa.
Joshua - Pendo
Bueno, supongo que su pregunta solo es útil para usted, solo habría algunos tipos que considerarían usar idiomas como hindi, tailandés, chino y árabe (este idioma tomará más de 1 byte para representar un carácter) en contra de sus idiomas requeridos. si está utilizando db, la utf8_general_ciclasificación es la forma adecuada de hacerlo.
Shushant
Estoy de acuerdo, tengo un poco de pista allí mismo. Gracias por señalar, también los caracteres de varios bits son lo suficientemente importantes como para ser mencionados en esta pregunta :)
Joshua - Pendo
5

Solo una respuesta secundaria: utilice absolutamente las URL traducidas con un identificador de idioma delante de ellas: http://www.domain.com/nl/over-ons Las
soluciones Hybride tienden a complicarse, por lo que me quedaría con ellas. ¿Por qué? Porque la url es esencial para el SEO.

Acerca de la traducción db: ¿El número de idiomas es más o menos fijo? ¿O más bien impredecible y dinámico? Si se soluciona, simplemente agregaría nuevas columnas, de lo contrario iría con varias tablas.

Pero en general, ¿por qué no usar Drupal? Sé que todos quieren construir su propio CMS porque es más rápido, más ágil, etc. etc. ¡Pero eso es realmente una mala idea!

Remy
fuente
1
Gracias por tu respuesta. La razón por la que no quiero usar Drupal / Joomla es simple: quiero asegurarme de que conozco todos los entresijos de mi sistema, fallas pausibles, cómo se construye el código (e importante: no construirlo por 300 programadores juntos) . Tengo razones más que suficientes para no elegir el código abierto. Además de eso, quiero que mi empresa sea un factor importante para mis clientes, es malo que puedan ir a cualquier otro desarrollador y dejarme sin nada.
Joshua - Pendo
77
Creo que todas estas razones están en disputa en toneladas de artículos. Es de esperar que sus clientes no lo elijan exactamente porque tiene un CMS patentado que nadie más puede mantener. Pero de todos modos, esa es una discusión totalmente diferente.
Remy
1
Entiendo su punto, todavía prefiero un sistema que conozco todos los entresijos y no siento nada por confiar en el trabajo de otra persona cuando uso un complemento.
Joshua - Pendo
1
Además, tiendo a documentar mi trabajo lo suficientemente bien, ya que soy un "ejército de un solo hombre" que las personas que trabajan para mí no deberían tener dificultades para conocer el sistema.
Joshua - Pendo
La mala idea es elegir Drupal e incluso Google dice que no les importa si la URL se traduce o no. Sin embargo, debe contener un identificador de configuración regional.
undefinedman
5

No voy a intentar refinar las respuestas ya dadas. En su lugar, le contaré sobre la forma en que mi propio marco PHP de OOP maneja las traducciones.

Internamente, mi framework usa códigos como en, fr, es, cn, etc. Una matriz contiene los idiomas admitidos por el sitio web: matriz ('en', 'fr', 'es', 'cn') El código de idioma se pasa a través de $ _GET (lang = fr) y, si no se pasa o no es válido, se establece en el primer idioma de la matriz. Entonces, en cualquier momento durante la ejecución del programa y desde el principio, se conoce el lenguaje actual.

Es útil comprender el tipo de contenido que debe traducirse en una aplicación típica:

1) mensajes de error de clases (o código de procedimiento) 2) mensajes sin error de clases (o código de procedimiento) 3) contenido de la página (generalmente almacenado en una base de datos) 4) cadenas de todo el sitio (como el nombre del sitio web) 5) script- cadenas específicas

El primer tipo es simple de entender. Básicamente, estamos hablando de mensajes como "no se pudo conectar a la base de datos ...". Estos mensajes solo necesitan cargarse cuando ocurre un error. Mi clase de administrador recibe una llamada de las otras clases y el uso de la información que se pasa como parámetros simplemente va a la carpeta de clase relevante y recupera el archivo de error.

El segundo tipo de mensaje de error es más parecido a los mensajes que recibe cuando la validación de un formulario salió mal. ("No puede dejar ... en blanco" o "elija una contraseña con más de 5 caracteres"). Las cadenas deben cargarse antes de que se ejecute la clase. Sé lo que es

Para el contenido real de la página, utilizo una tabla por idioma, cada tabla con el código del idioma como prefijo. Así que en_content es la tabla con contenido en inglés, es_content es para España, cn_content para China y fr_content es el material francés.

El cuarto tipo de cadena es relevante en todo su sitio web. Esto se carga a través de un archivo de configuración llamado usando el código para el idioma, es decir, en_lang.php, es_lang.php, etc. En el archivo de idioma global, deberá cargar los idiomas traducidos como array ('inglés', 'chino', 'español', 'francés') en el archivo global de inglés y array ('Anglais', 'Chinois', ' Espagnol ',' Francais ') en el archivo francés. Entonces, cuando llena un menú desplegable para la selección de idioma, está en el idioma correcto;)

Finalmente tienes las cadenas específicas del script. Entonces, si escribe una aplicación de cocción, podría ser "Su horno no estaba lo suficientemente caliente".

En mi ciclo de aplicación, el archivo de idioma global se carga primero. Allí encontrará no solo cadenas globales (como "Sitio web de Jack") sino también configuraciones para algunas de las clases. Básicamente todo lo que depende del idioma o la cultura. Algunas de las cadenas allí incluyen máscaras para fechas (MMDDYYYY o DDMMYYYY) o códigos de idioma ISO. En el archivo de idioma principal, incluyo cadenas para clases individuales porque hay muy pocas de ellas.

El segundo y último archivo de idioma que se lee del disco es el archivo de idioma del script. lang_en_home_welcome.php es el archivo de idioma para el script de inicio / bienvenida. Un script está definido por un modo (inicio) y una acción (bienvenido). Cada script tiene su propia carpeta con archivos de configuración y lang.

El script extrae el contenido de la base de datos nombrando la tabla de contenido como se explicó anteriormente.

Si algo sale mal, el administrador sabe dónde obtener el archivo de error dependiente del idioma. Ese archivo solo se carga en caso de error.

Entonces la conclusión es obvia. Piense en los problemas de traducción antes de comenzar a desarrollar una aplicación o marco. También necesita un flujo de trabajo de desarrollo que incorpore traducciones. Con mi marco, desarrollo todo el sitio en inglés y luego traduzco todos los archivos relevantes.

Solo una breve palabra final sobre la forma en que se implementan las cadenas de traducción. Mi marco tiene un único global, el $ manager, que ejecuta servicios disponibles para cualquier otro servicio. Entonces, por ejemplo, el servicio de formulario se apodera del servicio html y lo usa para escribir el html. Uno de los servicios en mi sistema es el servicio de traductor. $ translate-> set ($ service, $ code, $ string) establece una cadena para el idioma actual. El archivo de idioma es una lista de tales declaraciones. $ translate-> get ($ service, $ code) recupera una cadena de traducción. El código $ puede ser numérico como 1 o una cadena como 'no_connection'. No puede haber conflicto entre servicios porque cada uno tiene su propio espacio de nombres en el área de datos del traductor.

Publico esto aquí con la esperanza de salvar a alguien la tarea de reinventar la rueda como tenía que hacer hace unos años.

JG Estiot
fuente
4

Tuve la misma sonda hace un tiempo, antes de comenzar a usar el framework Symfony .

  1. Simplemente use una función __ () que tiene un parámetro pageId (u objectId, objectTable descrito en el n. ° 2), idioma de destino y un parámetro opcional de idioma de reserva (predeterminado). El idioma predeterminado podría establecerse en alguna configuración global para tener una forma más fácil de cambiarlo más tarde.

  2. Para almacenar el contenido en la base de datos, utilicé la siguiente estructura: (pageId, idioma, contenido, variables).

    • pageId sería un FK a su página que desea traducir. Si tiene otros objetos, como noticias, galerías o lo que sea, simplemente divídalo en 2 campos objectId, objectTable.

    • idioma: obviamente, almacenaría la cadena de idioma ISO EN_en, LT_lt, EN_us, etc.

    • contenido: el texto que desea traducir junto con los comodines para la sustitución de variables. Ejemplo "Hola, señor. %% name %%. El saldo de su cuenta es %% balance %%".

    • variables: las variables codificadas por json. PHP proporciona funciones para analizar rápidamente estos. Ejemplo "nombre: Laurynas, saldo: 15.23".

    • Mencionaste también campo babosa. puedes agregarlo libremente a esta tabla solo para tener una forma rápida de buscarlo.

  3. Las llamadas de su base de datos deben reducirse al mínimo con el almacenamiento en caché de las traducciones. Debe almacenarse en una matriz PHP, porque es la estructura más rápida en lenguaje PHP. Cómo va a hacer este almacenamiento en caché depende de usted. Según mi experiencia, debería tener una carpeta para cada idioma compatible y una matriz para cada ID de página. El caché debe reconstruirse después de actualizar la traducción. SOLO la matriz modificada debe regenerarse.

  4. creo que respondí eso en el # 2

  5. Tu idea es perfectamente lógica. Este es bastante simple y creo que no te hará ningún problema.

Las URL deben traducirse utilizando las babosas almacenadas en la tabla de traducción.

Ultimas palabras

Siempre es bueno investigar las mejores prácticas, pero no reinventar la rueda. simplemente tome y use los componentes de marcos conocidos y úselos.

Echa un vistazo al componente de traducción de Symfony . Podría ser un buen código base para ti.

Laurynas Mališauskas
fuente
Gracias por el comentario, también un +1 por tu tiempo. Laravel (en mi caso) está usando algunas partes de Symfony si no me equivoco, por lo que tiene toda la razón sobre no reinventar la rueda. Comencé esta pregunta (y generosidad) para obtener algunas ideas sobre la forma en que otros hacen las traducciones, estoy empezando a creer que hay muchas mejores prácticas :-)
Joshua - Pendo
1

Me he estado haciendo preguntas relacionadas una y otra vez, luego me perdí en los idiomas formales ... pero solo para ayudarte un poco, me gustaría compartir algunos hallazgos:

Recomiendo echar un vistazo a CMS avanzado

Typo3para PHP (sé que hay muchas cosas, pero esa es la que creo que es más madura)

Plone en Python

Si descubre que la web en 2013 debería funcionar de manera diferente, comience desde cero. Eso significaría reunir un equipo de personas altamente calificadas / experimentadas para construir un nuevo CMS. Puede ser que desee echar un vistazo al polímero para ese propósito.

Si se trata de codificación y sitios web multilingües / soporte de idioma nativo, creo que cada programador debería tener una pista sobre Unicode. Si no conoce Unicode, seguramente arruinará sus datos. No vaya con los miles de códigos ISO. Solo te ahorrarán un poco de memoria. Pero puedes hacer literalmente todo con UTF-8 incluso almacenar caracteres chinos. Pero para eso necesitaría almacenar caracteres de 2 o 4 bytes que lo hacen básicamente un utf-16 o utf-32.

Si se trata de la codificación de URL, nuevamente no debe mezclar codificaciones y tener en cuenta que al menos para el nombre de dominio hay reglas definidas por diferentes grupos de presión que proporcionan aplicaciones como un navegador. Por ejemplo, un dominio podría ser muy similar a:

ьankofamerica.com o bankofamerica.com lo mismo pero diferente;)

Por supuesto, necesita el sistema de archivos para trabajar con todas las codificaciones. Otra ventaja para unicode usando el sistema de archivos utf-8.

Si se trata de traducciones, piense en la estructura de los documentos. Por ejemplo, un libro o un artículo. Tienes las docbookespecificaciones para entender sobre esas estructuras. Pero en HTML se trata solo de bloques de contenido. Por lo tanto, le gustaría tener una traducción en ese nivel, también en el nivel de página web o de dominio. Entonces, si no existe un bloque, simplemente no está allí, si no existe una página web, se lo redirigirá al nivel de navegación superior. Si un dominio debe ser completamente diferente en la estructura de navegación, entonces ... es una estructura completamente diferente para administrar. Esto ya se puede hacer con Typo3.

Si se trata de frameworks, los más maduros que conozco, para hacer cosas generales como MVC (palabra de moda ¡Realmente lo odio! Como "performance" Si quieres vender algo, usa la palabra performance y featurerich y vendes ... qué el infierno) es Zend. Ha demostrado ser bueno llevar estándares a los codificadores de php chaos. Pero, typo3 también tiene un Framework además del CMS. Recientemente se ha reconstruido y ahora se llama flow3. Los marcos, por supuesto, cubren la abstracción de la base de datos, las plantillas y los conceptos para el almacenamiento en caché, pero tienen fortalezas individuales.

Si se trata de almacenamiento en caché ... eso puede ser tremendamente complicado / multicapa. En PHP pensará en acelerador, código de operación, pero también en html, httpd, mysql, xml, css, js ... cualquier tipo de caché. Por supuesto, algunas partes deben almacenarse en caché y las partes dinámicas, como las respuestas de blog, no. Algunos deben solicitarse a través de AJAX con las URL generadas. JSON, hashbangs, etc.

Luego, le gustaría tener cualquier pequeño componente en su sitio web para que sea accedido o administrado solo por ciertos usuarios , por lo que conceptualmente juega un papel importante.

También le gustaría hacer estadísticas , tal vez tener un sistema distribuido / un facebook de facebook, etc. cualquier software que se construya sobre sus cms superiores ... por lo que necesita diferentes tipos de bases de datos inmemory, bigdata, xml, lo que sea .

bueno, creo que eso es suficiente por ahora. Si no ha oído hablar de typo3 / plone o de los marcos mencionados, tiene suficiente para estudiar. En ese camino encontrarás muchas soluciones para preguntas que aún no has hecho.

Si luego piensa, hagamos un nuevo CMS porque es 2013 y php está a punto de morir de todos modos, entonces puede unirse a cualquier otro grupo de desarrolladores con la esperanza de no perderse.

¡Buena suerte!

Y por cierto. ¿Qué tal si la gente ya no tendrá sitios web en el futuro? y todos estaremos en google +? Espero que los desarrolladores se vuelvan un poco más creativos y hagan algo útil (para no ser asimilados por el borgle)

//// Editar /// Solo un poco para su aplicación existente:

Si tiene un CMS php mysql y desea incrustar soporte multilang. puede usar su tabla con una columna adicional para cualquier idioma o insertar la traducción con una identificación de objeto y una identificación de idioma en la misma tabla o crear una tabla idéntica para cualquier idioma e insertar objetos allí, luego haga una unión de selección si lo desea para que se muestren todos. Para la base de datos, use utf8 general ci y, por supuesto, en el frente / backend use utf8 text / encoding. He usado segmentos de ruta de URL para URL de la manera que ya explicaste como

domain.org/en/about puede asignar el ID de lang a su tabla de contenido. de todos modos, necesita tener un mapa de parámetros para sus URL, por lo que le gustaría definir un parámetro para mapear desde un segmento de ruta en su URL que sería, por ejemplo,

domain.org/en/about/employees/IT/administrators/

configuración de búsqueda

pageid | url

1 | /about/employees/../ ..

1 | /../about/employees../../

asignar parámetros a la ruta de URL ""

$parameterlist[lang] = array(0=>"nl",1=>"en"); // default nl if 0
$parameterlist[branch] = array(1=>"IT",2=>"DESIGN"); // default nl if 0
$parameterlist[employertype] = array(1=>"admin",1=>"engineer"); //could be a sql result 

$websiteconfig[]=$userwhatever;
$websiteconfig[]=$parameterlist;
$someparameterlist[] = array("branch"=>$someid);
$someparameterlist[] = array("employertype"=>$someid);
function getURL($someparameterlist){ 
// todo foreach someparameter lookup pathsegment 
return path;
}

por decir, eso ya se ha cubierto en la publicación superior.

Y para no olvidar, necesitaría "reescribir" la url en su archivo php generador que en la mayoría de los casos sería index.php

Dr. Dama
fuente
Gracias por el comentario, sin duda hay cosas en las que debería pensar. He estado usando la codificación utf8 durante un par de años ya, solía luchar con el personaje una vez ;-) Por otro lado, el tipo de CMS / Framework no debía ser un factor en su respuesta, ya que estaba buscando un método independiente de la plataforma como si estuviéramos codificando desde cero.
Joshua - Pendo
si realmente quieres codificar desde cero, te recomiendo echar un vistazo a Dartlang y el polímero. Dado que dartlang está trabajando en el navegador y tiene soporte de 32 y 64 bits y puede usarse para la mayoría de los propósitos en el lado del servidor y tiene un compilador dart2js que realmente vale la pena estudiar. Si las personas hablan sobre la independencia de la plataforma, piensan en Java ... sabemos lo que eso significa. Proceso de compilación ... Creo que usaría JSON para el intercambio. sitio web generado del lado del cliente con hashbangs y lado del servidor ... bueno, haga lo que quiera para asegurar la colaboración
Dr. Dama
La lógica de generación y base de datos es la tarea principal. Nadie va a hacer eso aquí por ti ... pero la Idea en sí es lo que cuenta. Como no me interesan los grupos de presión, pero para hacer las cosas, espero que puedas crear modelos y compartir algunas cosas. Estoy trabajando en tareas similares en este momento. Pero todavía estoy planeando. Estoy considerando Typo3 como un back-end y creo una nueva estructura de cliente. El patrón multilingüe se resuelve en el backend y compartirá información de manera dedicada para motores de búsqueda / servicios web. De todos modos, todo es sensible al contexto y una tarea de construcción continua
Dr. Dama
-1

Trabajo de base de datos:

Crear tabla de idiomas 'idiomas':

Campos:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

Cree una tabla en la base de datos 'contenido':

Campos:

content_id(primary and auto incremented)

main_content

header_content

footer_content

leftsidebar_content

rightsidebar_content

language_id(foreign key: referenced to languages table)

created_at

created_by

updated_at

updated_by

Trabajo frontal:

Cuando el usuario selecciona cualquier idioma del menú desplegable o cualquier área, guarde la identificación del idioma seleccionado en la sesión como,

$_SESSION['language']=1;

Ahora obtenga datos del 'contenido' de la tabla de la base de datos en función de la identificación del idioma almacenada en la sesión.

Los detalles se pueden encontrar aquí http://skillrow.com/multilingual-website-in-php-2/

usuario3445130
fuente
1
Esta es una forma de integración de lenguaje simple que se necesitaba, ¿incluso trataste de leer las publicaciones completas y las respuestas?
Joshua - Pendo
-2

Como persona que vive en Quebec, donde casi todo el sitio es francés e inglés ... he probado muchos, si no la mayoría, plugins multilingües para WP ... la única solución útil que funciona nivelada con todo mi sitio es mQtranslate ... ¡vivo y muero con eso!

https://wordpress.org/plugins/mqtranslate/

Menardmam
fuente
1
Sí, bueno, WP no fue un factor de la pregunta. Esto podría haber sido un comentario también
Joshua - Pendo
-3

¿Qué pasa con WORDPRESS + MULTI-LANGUAGE SITE BASIS(complemento)? el sitio tendrá estructura:

  • example.com/ eng / category1 / ....
  • example.com/ eng / my-page ....
  • example.com/ rus / category1 / ....
  • example.com/ rus / my-page ....

El complemento proporciona una interfaz para la traducción de todas las frases, con una lógica simple:

(ENG) my_title - "Hello user"
(SPA) my_title - "Holla usuario"

entonces se puede superar:
echo translate('my_title', LNG); // LNG is auto-detected

ps sin embargo, verifique si el complemento aún está activo.

T.Todua
fuente
3
y no es "Holla userio" en español es "Hola Usuario"
bheatcoker
1
Lol Holla userio, eso fue divertido!
spekdrum
por la razón que no sabía español (solo uso el ejemplo), ¡apúrate, apúrate a votar! :)
T.Todua
-5

Una opción realmente simple que funciona con cualquier sitio web donde pueda cargar Javascript es www.multilingualizer.com

Le permite poner todo el texto para todos los idiomas en una página y luego oculta los idiomas que el usuario no necesita ver. Funciona bien.

Paul Martin
fuente
¡Cuidado, el SEO sería muy malo! Además, carga todo el contenido mientras solo necesita una parte, lo cual es una práctica realmente mala.
Hafenkranich
cosas raras que el sitio solo está en inglés ... ¿por qué no usan su solución?
eduardo.lopes