4. Repasando
conceptos
Internacionalización
y
localización
5. Repasando
conceptos
Se podría decir:
I18n = Contenedor
y
L10n = contenido
6. Nuestro proyecto de
internacionalización
En sus orígenes
Servidores Windows
Tecnología .NET Con Acilia
SQL Server
Servidores Linux
Tecnología Open Source
MySQL
24. Buscando el
país
Utilizamos un listener:
¿Qué hace?
• Ejecutar sólo en peticiones maestras.
• Parsear url.
• Chequear país.
• Establecer valores predeterminados del
idioma (l10n).
25. 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
26. Buscando el
país
src/bnd/Listener/LangListener.php
class LanguageListener
{
public function __construct(RouterInterface $router, EntityManager $em)
{
$this->_router = $router;
$this->_em = $em;
}
public function onUrlParse(Event $event)
{
...
}
}
Contenido del listener
27. public function onUrlParse(Event $event)
{
// Ejecutar la lógica sólo si es una petición maestra
if ($event->getRequestType() !== SymfonyComponentHttpKernel
HttpKernel::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
28. Detección de IP
de usuario
Librerí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
29. 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
33. ¿Qué rutas i18n
podemos encontrar?
Peticiones de los usuarios:
http://natgeotv.com/uk/listings
http://natgeotv.com/fr/grille
http://natgeotv.com/nl/weekoverzicht
Acción de esas peticiones:
Controlador: Schedule
Acción: listings
Análisis del problema
36. Generación dinámica
de rutas
1. 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
37. Generación dinámica
de rutas
COUNTRY
id URL_WORD_TRANS
name
url id
... country_id
time_zone url_word_id
value
URL_WORD
id
value
Un poquito de base de datos
38. Generación dinámica
de rutas
COUNTRY
id url
15 uk URL_WORD_TRANS
6 fr id country_id url_word_id value
25 nl 1 15 7 listings
2 6 7 grille
URL_WORD
3 25 7 weekoverzicht
id value
7 listings
Un poquito de base de datos
39. Generación dinámica
de rutas
COUNTRY
id url
15 uk URL_WORD_TRANS
6 fr id country_id url_word_id value
25 nl 1 15 7 listings
2 6 7 grille
URL_WORD
3 25 7 weekoverzicht
id value
7 listings
Un poquito de base de datos
40. ¿Qué rutas i18n
podemos encontrar?
Peticiones de los usuarios:
http://natgeotv.com/uk/listings
http://natgeotv.com/fr/grille
http://natgeotv.com/nl/weekoverzicht
Acción de esas peticiones:
Controlador: Schedule
Acción: listings
Análisis del problema
41. Modificando la
generación de rutas
Interceptar 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
42. Modificando la
generación de rutas
Interceptar 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
43. 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
44. Modificando la
generación de rutas
Extendemos 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
45. Modificando la
generación de rutas
class 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
51. ¿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
52. ¿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
53. ¿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
54. Modificando la
localización de las vistas
Interceptar 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
55. Modificando la
localización de las vistas
Interceptar 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
56. 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
57. Modificando la
localización de las vistas
Extendemos 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
58. Modificando la
localización de las vistas
Extendemos 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
59. Modificando la
localización de las vistas
class 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
60. Resultado
El á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
61. Resultado
Hong Kong
Rutas de directorios disponibles
62. Resultado
Hong Kong
Reino Unido
Rutas de directorios disponibles
63. Resultado
Hong Kong
Reino Unido
Rutas de directorios disponibles
65. 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
66. Lo que no hay que
dejar pasar
Conversión de texto a mayúscula:
{{ ' ' | upper }}
Pequeñas soluciones
67. Lo que no hay que
dejar pasar
Conversión de texto a mayúscula:
{{ ' ' | upper }}
Debería imprimir:
Pequeñas soluciones
68. Lo que no hay que
dejar pasar
Conversión de texto a mayúscula:
{{ ' ' | upper }}
Debería imprimir:
¡PERO NO! Imprime: SIBER SIRLAR
Pequeñas soluciones
69. Lo que no hay que
dejar pasar
Conversión de texto a mayúscula:
{{ ' ' | upper }}
Debería imprimir:
¡PERO NO! Imprime: SIBER SIRLAR
Pequeñas soluciones
70. Lo que no hay que
dejar pasar
Solució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
74. ¿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
75. 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!