I18n DeSymfony

3,859 views

Published on

35 en 1, internacionalización al límite con sf2

1 Comment
2 Likes
Statistics
Notes
No Downloads
Views
Total views
3,859
On SlideShare
0
From Embeds
0
Number of Embeds
2,231
Actions
Shares
0
Downloads
63
Comments
1
Likes
2
Embeds 0
No embeds

No notes for slide

I18n DeSymfony

  1. 1. ¿Quién soy?• Cristina Quintana (@jautu)• Soy de Córdoba• Desarrolladora web en Acilia
  2. 2. Repasando conceptosInternacionalización y localización
  3. 3. Repasando conceptosSe podría decir: I18n = Contenedor y L10n = contenido
  4. 4. Nuestro proyecto de internacionalizaciónEn sus orígenesServidores WindowsTecnología .NET Con AciliaSQL Server Servidores Linux Tecnología Open Source MySQL
  5. 5. Nuestro proyecto deinternacionalización
  6. 6. Nuestro proyecto deinternacionalización
  7. 7. Nuestro proyecto deinternacionalización
  8. 8. Nuestro proyecto deinternacionalizaciónGrecia
  9. 9. Nuestro proyecto deinternacionalizaciónGrecia Reino Unido
  10. 10. Nuestro proyecto deinternacionalizaciónGrecia Reino Unido Turquía
  11. 11. Nuestro proyecto deinternacionalizaciónGrecia Reino Unido Turquía
  12. 12. Nuestro proyecto deinternacionalización Israel
  13. 13. Nuestro proyecto de internacionalizaciónIsrael Israel Italia
  14. 14. Nuestro proyecto de internacionalizaciónIsrael Israel Italia E. Árabes
  15. 15. Nuestro proyecto de internacionalización <html dir=""> •  ltr (left-to-right) •  rtl (right-to-left)Por supuesto, cada caso necesita unas cssdistintas.
  16. 16. Planificación del proyecto•  Migración de la base de datos•  Construcción del backend•  Construcción del frontend•  Nueva infraestructura
  17. 17. Planificación del proyecto•  Migración de la base de datos•  Construcción del backend•  Construcción del frontend (sf2)•  Nueva infraestructura
  18. 18. ¿Cómo hicimos el frontend? Mode translation: On
  19. 19. Flujo de trabajoPetición Controlador Respuesta Vista
  20. 20. Flujo de trabajo_locale SIexiste SI Procesar Construir ruta acción vista
  21. 21. Flujo de trabajo_locale SIexiste SI Procesar Construir ruta acción vista
  22. 22. Buscando el paísUtilizamos un listener: ¿Qué hace?•  Ejecutar sólo en peticiones maestras.•  Parsear url.•  Chequear país.•  Establecer valores predeterminados del idioma (l10n).
  23. 23. Buscando el país src/bndl/Resources/config/services.xml<services> ... <service id="bndl.lang_listener" class="%bndl.lang_listener.class%"> <tag name="kernel.event_listener" event="kernel.request" method="onUrlParse"/> <argument type="service" id="router"/> <argument type="service" id="doctrine.orm.entity_manager"/> </service> ...</services> Declaración del listener
  24. 24. Buscando el país src/bnd/Listener/LangListener.phpclass LanguageListener{ public function __construct(RouterInterface $router, EntityManager $em) { $this->_router = $router; $this->_em = $em; } public function onUrlParse(Event $event) { ... }} Contenido del listener
  25. 25. public function onUrlParse(Event $event) { // Ejecutar la lógica sólo si es una petición maestra if ($event->getRequestType() !== SymfonyComponentHttpKernelHttpKernel::MASTER_REQUEST) { return; } ... // Busqueda del código de país en la url $parameters = $this->router->match($request->getPathInfo()); ... // Si existe el código del país ... // Si el código del país no exite ... } Contenido del listener
  26. 26. Detección de IP de usuarioLibrería Maxmind: •  Ofrece una detección más que razonable en su versión gratuita (GeoLite)X-Forwarded-For header: •  Al pasar por Varnish, load balancers…. La IP original del usuario se pierde.Necesitamos que nuestra aplicación utilice esa IP Curiosidades
  27. 27. Detección de IP de usuario SymfonyComponentHttpFoundationRequest.php// Consulta el parámetro REMOTE_ADDR$this->request->getClientIp();// Consulta tanto HTTP_CLIENT_IP// como HTTP_X_FORWARDED_FOR$this->request->getClientIp( true ); Curiosidades
  28. 28. Flujo de trabajo_locale SIexiste SI Procesar Construir ruta acción vista
  29. 29. Flujo de trabajo_locale SIexiste SI Procesar Construir ruta acción vista
  30. 30. ¿Rutas i18n?
  31. 31. ¿Qué rutas i18n podemos encontrar?Peticiones de los usuarios: http://natgeotv.com/uk/listings http://natgeotv.com/fr/grille http://natgeotv.com/nl/weekoverzichtAcción de esas peticiones: Controlador: Schedule Acción: listings Análisis del problema
  32. 32. ¿Cómo podemos generar esas rutas? src/mybundle/Resources/config/routing.ymlBndl_listings_uk: pattern: /{_locale}/listings defaults: { _controller: "Bndl:Schedule:listings" } requirements: _locale: ukBndl_listings_nl: pattern: /{_locale}/weekoverzicht defaults: { _controller: "Bndl:Schedule:listings" } requirements: _locale: nl ¿Generación manual de rutas?
  33. 33. ¿Cómo podemos generar esas rutas? src/mybundle/Resources/config/routing.ymlBndl_listings_uk: pattern: /{_locale}/listings defaults: { _controller: "Bndl:Schedule:listings" } requirements: _locale: uk ¿Estamos locos?Bndl_listings_nl: pattern: /{_locale}/weekoverzicht defaults: { _controller: "Bndl:Schedule:listings" } requirements: _locale: nl ¿Generación manual de rutas?
  34. 34. Generación dinámica de rutas1.  Definir la ruta genérica: Bndl_listings: pattern: /{_locale}/listings defaults: { _controller: "Bndl:Schedule:listings" }2.  Modificar el routing para la generación automática de dicha ruta para todos los países. Plan de acción
  35. 35. Generación dinámica de rutasCOUNTRYid URL_WORD_TRANSnameurl id... country_idtime_zone url_word_id valueURL_WORDidvalue Un poquito de base de datos
  36. 36. Generación dinámica de rutas COUNTRYid url15 uk URL_WORD_TRANS6 fr id country_id url_word_id value25 nl 1 15 7 listings 2 6 7 grilleURL_WORD 3 25 7 weekoverzichtid value7 listings Un poquito de base de datos
  37. 37. Generación dinámica de rutas COUNTRYid url15 uk URL_WORD_TRANS6 fr id country_id url_word_id value25 nl 1 15 7 listings 2 6 7 grilleURL_WORD 3 25 7 weekoverzichtid value7 listings Un poquito de base de datos
  38. 38. ¿Qué rutas i18n podemos encontrar?Peticiones de los usuarios: http://natgeotv.com/uk/listings http://natgeotv.com/fr/grille http://natgeotv.com/nl/weekoverzichtAcción de esas peticiones: Controlador: Schedule Acción: listings Análisis del problema
  39. 39. Modificando la generación de rutasInterceptar el momento en el que se lee la configuración de las rutas:<services> ... <service id="bndl.routing.loader.yml" class="%bndl.routing.loader.yml.class%" public="false"> <tag name="routing.loader" /> <argument type="service" id="service_container" /> <argument type="service" id="bndl.file_locator" /> </service> <service id="routing.loader.yml" alias="bndl.routing.loader.yml" /> ...</services> Adaptación de la estructura
  40. 40. Modificando la generación de rutasInterceptar el momento en el que se lee la configuración de las rutas:<services> ... <service id="bndl.routing.loader.yml" class="%bndl.routing.loader.yml.class%" public="false"> <tag name="routing.loader" /> <argument type="service" id="service_container" /> <argument type="service" id="bndl.file_locator" /> </service> <service id="routing.loader.yml" alias="bndl.routing.loader.yml" /> ...</services> Adaptación de la estructura
  41. 41. Modificando la generación de rutas¿Qué hace la clase original?namespace SymfonyComponentRoutingLoader;class YamlFileLoader extends FileLoader{ protected function parseRoute(RouteCollection $collection, $name, $config, $file) { ... $route = new Route($config[pattern], $defaults, $requirements, $options); $collection->add($name, $route); }} Aprendiendo de sf2
  42. 42. Modificando la generación de rutasExtendemos el servicio original:use SymfonyComponentRoutingLoaderYamlFileLoader as BaseYamlFileLoader;class YamlFileLoader extends BaseYamlFileLoader{ protected function parseRoute(RouteCollection $collection, $name, $config, $file) { $url_words_trans = ...; // Consulta a la tabla url_words $country = ...; // Consulta a la tabla country ... $route = new I18nRoute(..., $url_words_trans, $country); $collection->addCollection($route->getCollection()); }} Adaptando la arquitectura
  43. 43. Modificando la generación de rutasclass I18nRoute { public function __construct($original_pattern, $defaults = array(), $requirements = array(), $options = array(), $translations = array(), $regions = array()) { // Construccion a partir del patrón original, sus equivalentes por país $patterns = ...; // Formato: array(_locale => pattern) ... //Construcción de una ruta para cada patrón nuevo foreach ($patterns as $country_url => $i18n_pattern) { $requirements[_locale] = $country_url; $this->collection->add($name . _ . country_url, new Route($i18n_pattern, $defaults, $requirements, $options, true)); } $this->collection->add($name, new Route($original_pattern, $defaults, $requirements, $options, true)); }} Adaptando la arquitectura
  44. 44. ResultadoBndl_listings_uk: pattern: /{_locale}/listings defaults: { _controller: ”Bndl:Schedule:listings" } requirements: _locale: ukBndl_listings_nl: pattern: /{_locale}/weekoverzicht defaults: { _controller: ”Bndl:Schedule:listings" } requirements: _locale: nlBndl_listings: pattern: /{_locale}/listings defaults: { _controller: ”Bndl:Schedule:listings" } Rutas disponibles
  45. 45. Flujo de trabajo_locale SIexiste SI Procesar Construir ruta acción vista
  46. 46. Flujo de trabajo_locale SIexiste SI Procesar Construir ruta acción vista
  47. 47. Flujo de trabajo_locale SIexiste SI Procesar Construir ruta acción vista
  48. 48. ¿Vistas i18n?
  49. 49. ¿Dónde pueden estar mis vistas? app/Resources/Bndl/views src/Bndl/Resources/views¿Qué hacer si tenemos requisitos específicos para un país?: Llenar el código de {% if %} Análisis del problema
  50. 50. ¿Dónde pueden estar mis vistas? app/Resources/Bndl/views src/Bndl/Resources/views¿Qué hacer si tenemos requisitos específicos para un país?: Llenar el código de ¡Acaben ya conmigo! {% if %} Análisis del problema
  51. 51. ¿Dónde pueden estar mis vistas?¿Alguna idea mejor?:•  Se puede utilizar la plantilla que se encuentre primero en este nuevo árbol de directorios src/Bndl/Resources/views/{_locale} src/Bndl/Resources/views/%DIR_BASE% src/Bndl/Resources/views Análisis del problema
  52. 52. Modificando la localización de las vistasInterceptar el momento cuando crear el árbol de directorios:<services> ... <service id="bnbl.file_locator" class="%bnbl.file_locator.class%"> <argument type="service" id="kernel" /> <argument>%kernel.root_dir%/Resources</argument> </service> <service id="file_locator" alias="bnbl.file_locator" /> ...</services> Adaptación de la estructura
  53. 53. Modificando la localización de las vistasInterceptar el momento cuando crear el árbol de directorios:<services> ... <service id="bnbl.file_locator" class="%bnbl.file_locator.class%"> <argument type="service" id="kernel" /> <argument>%kernel.root_dir%/Resources</argument> </service> <service id="file_locator" alias="bnbl.file_locator" /> ...</services> Adaptación de la estructura
  54. 54. Modificando la localización de las vistas¿Qué hace la clase original?:namespace SymfonyComponentHttpKernelConfig;class FileLocator extends BaseFileLocator{ public function locate($file, $currentPath = null, $first = true) { ... return $this->kernel->locateResource($file, $this->path, $first); ... }} Adaptando la arquitectura
  55. 55. Modificando la localización de las vistasExtendemos el servicio original:use SymfonyComponentHttpKernelConfigFileLocator as BaseFileLocator;class FileLocator extends BaseFileLocator{ public function locate($file, $currentPath = null, $first = true) { ... return $this->locateResource($file, $this->path, $first); ... }} Adaptando la arquitectura
  56. 56. Modificando la localización de las vistasExtendemos el servicio original:use SymfonyComponentHttpKernelConfigFileLocator as BaseFileLocator;class FileLocator extends BaseFileLocator{ public function locate($file, $currentPath = null, $first = true) { ... return $this->locateResource($file, $this->path, $first); ... }} Adaptando la arquitectura
  57. 57. Modificando la localización de las vistasclass FileLocator extends BaseFileLocator{ // Lógica de la clase SymfonyComponentHttpKernelKernel public function locateResource($name, $dir = null, $first = true) { $myDir = ...; // Nombre del directorio para las vistas del país foreach ( $bundles as $bundle ) { $checkPaths = ...; // Rutas relativas a chequear foreach ( $checkPaths as $checkPath) { if ( file_exists($file = $checkPath)) { // Verificación de la plantilla } } } ... }} Adaptando la arquitectura
  58. 58. ResultadoEl árbol de directorios en el que se chequean la existencia de las plantillas es: app/Resources/Bndl/views/{_locale} app/Resources/Bndl/views/%DIR_BASE% app/Resources/Bndl/views src/Bndl/Resources/views/{_locale} src/Bndl/Resources/views/%DIR_BASE% src/Bndl/Resources/views Rutas de directorios disponibles
  59. 59. Resultado Hong KongRutas de directorios disponibles
  60. 60. Resultado Hong Kong Reino UnidoRutas de directorios disponibles
  61. 61. Resultado Hong Kong Reino UnidoRutas de directorios disponibles
  62. 62. ¿Quebraderos de cabeza?
  63. 63. Lo que no hay que dejar pasar•  Establecer la zona horaria correcta.•  Formatos de fechas.•  Conversión de texto a mayúscula.•  Cachear lo no cacheable.•  Especificar el encoding en la configuración de los servicios que acceden a base de datos.•  ... Pequeñas soluciones
  64. 64. Lo que no hay que dejar pasarConversión de texto a mayúscula: {{ | upper }} Pequeñas soluciones
  65. 65. Lo que no hay que dejar pasarConversión de texto a mayúscula: {{ | upper }}Debería imprimir: Pequeñas soluciones
  66. 66. Lo que no hay que dejar pasarConversión de texto a mayúscula: {{ | upper }}Debería imprimir: ¡PERO NO! Imprime: SIBER SIRLAR Pequeñas soluciones
  67. 67. Lo que no hay que dejar pasarConversión de texto a mayúscula: {{ | upper }}Debería imprimir: ¡PERO NO! Imprime: SIBER SIRLAR Pequeñas soluciones
  68. 68. Lo que no hay que dejar pasarSolución: Sobreescribir el método upper de twig.class TextExtension extends Twig_Extension{ public function upper(Twig_Environment $env, $sentence) { $value = str_replace(ı, I, str_replace(i, İ, $sentence)); if ( null !== ( $charset = $env->getCharset() ) ) { return mb_strtoupper($value, $charset); } return strtoupper($value); }} Pequeñas soluciones
  69. 69. Infraestructura
  70. 70. Load Balancer Varnish (n)Media Frontend (n) Backend (n) | BBDD
  71. 71. Deploy y más deploy
  72. 72. ¿Cómo manejamos los entornos?•  Tenemos múltiples servidores que coordinar y monitorizar.•  Necesitamos una herramienta que actúe sobre todos.•  Nuestra elección Magallanes, que sería como el “Capistrano de PHP”, creado por @andres_montanez, miembro del equipo. Análisis de la situación
  73. 73. Magallanes, ¡hay que probarlo!•  Posibilidad de configurar múltiples “targets”•  Posibilidad de crear “pre-tasks”•  Integración con git•  Rollback instantáneo•  Posibilidad de post-tasks•  Configuración de número de releases a archivar.•  Posibilidad de sobreescribir realeases. ¡Feliz deploy a todos!
  74. 74. Muchas gracias @jautu quintana.cano@gmail.comhttp://es.linkedin.com/in/cristinaquintanacano Contacto

×