Agregando internacionalización "i18n" a tus posts en Ghost
En este post explico en detalle mi experiencia y el método que tuve que implementar para tener la opción de poder escribir posts con Ghost en varios idiomas, en mi caso Ingles y Español.
Tabla de contenidos
- Historia de este post
- Instalar Ghost
- Configurar memoria Swap
- Instalar dependencias npm de Ghost
- Configurar Ghost
- Instalar Nginx
- Crear y configurar otra instancia de Ghost para el idioma adicional
- Modificar el archivo de configuración de Nginx
- Encender ambas instancias de Ghost
- Cambiar la ruta del post por defecto en nuestra instancia de Ghost para Español
Historia de este post
Cuando se es bilingüe y también se quiere comenzar en el mundo del blogin con Ghost, puedes llegar a la siguiente pregunta: ¿Como puedo escribir posts en varios idiomas con esta genial herramienta de código abierto que es Ghost?.
Esa fue la pregunta que yo me hice al iniciar el desarrollo de este blog debido a que quería aprovechar las diferentes audiencias de mi area profesional Ingeniería del Software, que existen tanto en el idioma Ingles como en el Español y que estas audiencias se beneficiaran de mi contenido de alto valor que iba a compartir en mis posts.
Lo primero que hice como todo buen auto-didacta fue buscar en Google:
- ¿Como agregar i18n a Ghost?
- ¿Como escribir un post en varios idiomas en Ghost?
Pero... los resultados no fueron los esperados, encontré este issue de Github [Epic] Ghost and i18n abierto desde el 2014... solo con ver la fecha de apertura ya me daba un mal presentimiento, aún así seguí leyendo comentario a comentario de ese issue y vi como explicaban los miembros de la comunidad las diferentes propuestas para agregar i18n al código de Ghost, más que todo, que herramientas utilizar, que patrones seguir, entre otras cosas.
Finalmente me di cuenta de que ese issue/epic era para el soporte de multiples idiomas pero del admin de Ghost, no para los posts en general.
Como última instancia decidí unirme al Slack de Ghost y preguntar directamente por allí a las personas que tienen más tiempo y experiencia que yo utilizando Ghost. La respuesta fue que todavía no existía soporte oficial para i18n en los posts. Así que decidí tomar acciones en el asunto y crear algo por mi propia cuenta para hacer esto posible.
Crear un plugin desde cero iba a tomarme mucho tiempo, por que primero tenía que analizar a cierto bajo nivel la forma en la que esta construído Ghost para luego en base a eso desarrollar el plugin, pero rápidamente deseché esta opción por el factor tiempo.
La segunda opción que se me ocurrió fue la que implementé y es la siguiente: Levantar dos instancias de Ghost en puertos diferentes y con los proxies de Nginx redireccionar dependiendo del idioma del usuario a una instancia u otra, luego crear un objeto clave/valor donde se almacenen las urls de ambas instancias y agregar un botón con la opción de cambio de idioma.
A continuación explicaré detalladamente el proceso para lograr esta segunda opción de i18n, para este ejemplo he utilizado un contenedor de DigitalOcean específicamente el estándar de 5$ al mes con el sistema operativo Ubuntu 16.04 64 bits y NodeJs v6.9.2.
Instalar Ghost
Lo primero que debemos hacer es instalar Ghost y para esto es recomendado instalarlo en la ruta /var/www/ghost
. Vamos a instalar los paquetes zip
y wget
que usaremos para descargar Ghost, después debemos crear el directorio /var/www/
donde descargaremos la ultima versión de Ghost desde su repositorio de Github:
sudo apt-get update
sudo apt-get install zip wget
sudo mkdir -p /var/www/
cd /var/www/
sudo wget https://ghost.org/zip/ghost-latest.zip
Ahora que hemos obtenido la ultima version de Ghost, tenemos que descomprimirlo. También cambiaremos nuestro directorio a /var/www/ghost/
.
sudo unzip -d ghost ghost-latest.zip
cd ghost/
Configurar memoria Swap
Este paso es solo para aquellos que seleccionaron el contenedor de 512 de RAM de DigitalOcean, para los que no siguen el post con el contenedor, pueden continuar con el paso: Instalar dependencias npm de Ghost
Antes de instalar las dependencias npm de Ghost debemos habilitar memoria de tipo Swap, esto para evitar que el sistema operativo detenga el proceso de npm install por falta de memoria.
Swap es un area en el disco duro que ha sido diseñado como un lugar donde el sistema operativo puede temporalmente almacenar información que no puede retener en la memoria RAM. Básicamente, esto te da la habilidad de incrementar la cantidad de información que tu servidor puede mantener en la memoria con algunas limitaciones. El espacio en el disco duro sera usado principalmente cuando el espacio en la memoria RAM sea insuficiente.
Creando el archivo Swap
Crearemos un archivo llamado swapfile en nuestro directorio raíz (/). El archivo debe asignar la cantidad de espacio que queremos para nuestro archivo swap.
Podemos crear un archivo de 2 Gigabytes escribiendo:
sudo fallocate -l 2G /swapfile
Podemos verificar que la cantidad correcta de espacio fue reservada escribiendo:
ls -lh /swapfile
El comando anterior debería imprimir algo como esto:
-rw-r--r-- 1 root root 2.0G Feb 26 17:52 /swapfile
Habilitando el archivo Swap
Actualmente nuestro archivo esta creado, pero nuestro sistema operativo no sabe que este esta destinado para usar como memoria Swap. Necesitamos darle formato a este archivo como Swap y luego habilitarlo. Aunque antes de hacer esto, necesitamos ajustar los permisos en nuestro archivo para que de este modo no sea leíble por alguien mas que no sea root. Permitir a otros usuarios leer o escribir en este archivo puede ser un alto riesgo de seguridad.
Necesitamos bloquear los permisos y para eso escribimos:
sudo chmod 600 /swapfile
Ahora verificamos que el archivo tiene los permisos correctos escribiendo:
ls -lh /swapfile
El comando anterior debería imprimir algo como esto:
-rw------- 1 root root 2.0G Feb 26 17:52 /swapfile
Como pueden ver, únicamente las columnas para el usuario root tienen las propiedades de lectura y escritura habilitadas.
Ahora que nuestro archivo es más seguro, podemos decirle a nuestro sistema operativo que establezca el espacio swap con el comando:
sudo mkswap /swapfile
Nuestro archivo esta listo para ser usado como espacio swap. Podemos habilitarlo con el comando:
sudo swapon /swapfile
Podemos verificar que nuestro procedimiento fue satisfactorio con el comando:
sudo swapon -s
Nuestro espacio Swap ha sido establecido satisfactoriamente en nuestro sistema operativo y comenzará a ser usado cuando sea necesario.
Hacer el archivo Swap permanente
Tenemos nuestro espacio Swap habilitado, pero cuando reiniciamos, el server no lo habilitará automáticamente. Podemos cambiar esto mediante la modificación del archivo fstab
.
Editamos el archivo con privilegios root en nuestro editor de texto:
sudo nano /etc/fstab
Al final del archivo, necesitamos agregar una nueva linea que le dirá al sistema operativo que use nuestro archivo Swap automáticamente.
/swapfile none swap sw 0 0
Guardamos y cerramos el archivo una vez hemos terminado.
Instalar dependencias npm de Ghost
Ahora podemos instalar las dependencias de Ghost y los módulos de node (únicamente las dependencias para producción).
sudo npm install --production
Ghost quedará instalado cuando esto se completa. Necesitamos configurar Ghost antes de poder iniciarlo.
Configurar Ghost
El archivo de configuración de Ghost debería estar localizado en /var/www/ghost/config.js
. Sin embargo ese archivo no viene con Ghost por defecto, en vez de ese, la instalación incluye config.example.js
, entonces copiaremos el ejemplo de configuración en la ubicación correcta.
Nos aseguramos de copiarlo en vez de moverlo en caso de que necesitemos revertir los cambios siempre tendremos el archivo de configuración por defecto.
sudo cp config.example.js config.js
Abrimos el archivo en la terminal:
sudo nano config.js
Tienes que cambiar el valor de la url al de tu dominio (o podrías usar la dirección IP de tu servidor en el caso de que no quieras utilizar un dominio). Este valor debe estar en el formato de una URL. Por ejemplo, http://ejemplo.com/
o http://la_ip_de_tu_servidor/
. Si este valor no esta con el formato correcto, Ghost no podrá iniciar.
Cambia también el valor del host en la sección del servidor a 0.0.0.0
.
Guarda el archivo y sal del editor de texto nano presionando CTRL + X
luego escribes Y
y finalmente presionas ENTER.
Instalar Nginx
El siguiente paso es instalar Nginx. El cual básicamente nos va a permitir conectarnos a Ghost a través del puerto 80
y agregar los correspondientes proxies para cada idioma.
Lo instalamos con el siguiente comando:
sudo apt-get install nginx
Lo siguiente que tendremos que hacer es configurar Nginx y por ello movernos al directorio /etc/nginx
y eliminar el archivo por defecto en /etc/nginx/sites-enabled
cd /etc/nginx/
sudo rm sites-enabled/default
Crearemos un nuevo archivo en /etc/nginx/sites-available/
llamado ghost y lo abriremos con nano para editarlo:
sudo touch /etc/nginx/sites-available/ghost
sudo nano /etc/nginx/sites-available/ghost
Pega el siguiente código en el archivo y cambia solo el nombre del server con el nombre de tu dominio o la dirección IP de tu servidor en caso de que no quieras agregar un dominio.
server {
listen 80;
server_name your_domain_name.com;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For
$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:2368;
}
}
Guarda el archivo y sal del editor de texto nano presionando CTRL + X
luego escribes Y
y finalmente presionas ENTER.
Ahora vamos a crear un enlace simbólico a nuestra configuración en sites-enabled:
sudo ln -s /etc/nginx/sites-available/ghost /etc/nginx/sites-enabled/ghost
Vamos a reiniciar Nginx:
sudo service nginx restart
Ahora necesitamos iniciar Ghost:
cd /var/www/ghost
npm start --production
Deberías ser capaz de acceder a tu blog en el puerto 80
como http://la_ip_de_tu_servidor/
o http://tu_nombre_de_dominio/
.
Crear y configurar otra instancia de Ghost para el idioma adicional
En caso de tener Ghost corriendo, presionamos Ctrl + C
o Command + C
para detener el proceso. Ahora procederemos a crear otra instancia de Ghost, para esto, estando situados en /var/www/ghost
crearemos una carpeta llamada en
y moveremos todo el contenido hacia ella:
mkdir en
mv * en
Vamos a crear una carpeta llamada es
y copiaremos todo el contenido de la carpeta en
a la carpeta es
:
mkdir es
cp -r en/* es/
Usaremos la carpeta en
para el idioma Ingles y la carpeta es
para el idioma Español. Necesitamos modificar algunos archivos dentro de la carpeta es
para agregarle el soporte multi idioma, lo primero que modificaremos será el archivo de configuración de Ghost:
cd es/
sudo nano config.js
Tenemos que modificar la propiedad url del objeto de producción, así como también la propiedad server, en url colocaremos http://la_ip_de_tu_servidor/es/blog
y dentro de la propiedad server modificaremos el puerto "port" por 2369
. Esto lo realizamos con el propósito de que nuestra url de cada instancia de Ghost siga la buena practica SEO de cargar el contenido del blog identificado por el nombre del país en el estándar ISO 3166-1
, así como también al momento de iniciar ambas instancias cada una corra en un puerto diferente.
Guardamos el archivo y salimos del editor de texto nano presionando CTRL + X
luego escribimos Y
y finalmente presionamos ENTER.
El objeto de producción debería quedar así:
production: {
url: 'http://your_server_ip/es/blog',
mail: {},
database: {
client: 'sqlite3',
connection: {
filename: path.join(__dirname, '/content/data/ghost.db')
},
debug: false
},
server: {
host: '0.0.0.0',
port: '2369'
}
}
La modificación anterior al archivo de configuración, debemos realizarla también en la carpeta de ingles en
:
Directorio en/config.js
production: {
url: 'http://your_server_ip/en/blog',
mail: {},
database: {
client: 'sqlite3',
connection: {
filename: path.join(__dirname, '/content/data/ghost.db')
},
debug: false
},
server: {
host: '0.0.0.0',
port: '2368'
}
}
Vamos a agregar la parte HTML correspondiente para mostrar el icono de cambio de idiomas, para esto debemos situarnos en la carpeta casper el cual es el tema por defecto que trae Ghost content/themes/casper/
:
cd content/themes/casper/
El siguiente código HTML lo agregaremos justo debajo del botón de Menu que se muestra en la esquina superior derecha de la interfaz y dentro de la etiqueta <nav></nav>
en los archivos index.hbs
, post.hbs
, tag.hbs
y author.hbs
estas modificaciones las haremos tanto en la carpeta de es
para español, como en la carpeta en
para ingles:
Version para español - Directorio es/content/themes/casper/
<dl class="i18n">
<dt><a href="javascript:void(0);"><span>Idioma</span></a></dt>
<dd>
<ul>
<li><a href="{{url}}">Español</a></li>
<li><a href="#">Ingles</a></li>
</ul>
</dd>
</dl>
Version para ingles - Directorio en/content/themes/casper/
<dl class="i18n">
<dt><a href="javascript:void(0);"><span>Language</span></a></dt>
<dd>
<ul>
<li><a href="{{url}}">English</a></li>
<li><a href="#">Spanish</a></li>
</ul>
</dd>
</dl>
La sección de nav de esos archivos debería quedar algo como esto:
Version para español
<nav class="main-nav overlay clearfix">
{{#if @blog.logo}}<a class="blog-logo" href="{{@blog.url}}"><img src="{{@blog.logo}}" alt="{{@blog.title}}" /></a>{{/if}}
{{#if @blog.navigation}}
<a class="menu-button icon-menu" href="#"><span class="word">Menu</span></a>
{{/if}}
<dl class="i18n">
<dt><a href="javascript:void(0);"><span>Idioma</span></a></dt>
<dd>
<ul>
<li><a href="{{url}}">Español</a></li>
<li><a href="#">Ingles</a></li>
</ul>
</dd>
</dl>
</nav>
Version para ingles
<nav class="main-nav overlay clearfix">
{{#if @blog.logo}}<a class="blog-logo" href="{{@blog.url}}"><img src="{{@blog.logo}}" alt="{{@blog.title}}" /></a>{{/if}}
{{#if @blog.navigation}}
<a class="menu-button icon-menu" href="#"><span class="word">Menu</span></a>
{{/if}}
<dl class="i18n">
<dt><a href="javascript:void(0);"><span>Language</span></a></dt>
<dd>
<ul>
<li><a href="{{url}}">English</a></li>
<li><a href="#">Spanish</a></li>
</ul>
</dd>
</dl>
</nav>
El siguiente paso es agregar los estilos correspondientes para que el icono de cambio de idioma se vea bien tanto en computadores de escritorio como en tabletas y teléfonos mobiles.
Para esto vamos a abrir el archivo screen.css
ubicado en es/content/themes/casper/assets/css/screen.css
y en/content/themes/casper/assets/css/screen.css
justo después de las animaciones agregaremos el siguiente código de estilos:
/* ===============================================================
15. i18n styles
=============================================================== */
dl.i18n, .i18n dd, .i18n dt {
margin:0px;
padding:0px;
width:100px;
border-radius: 3px;
}
dl.i18n {
margin-right:10px;
}
.i18n dd {
position:relative;
}
.i18n dt a {
color: #fff;
height:36px;
text-decoration: none;
font-family: 'Open Sans', sans-serif;
line-height: 1.75em;
background:transparent;
display:block;
font-size: 1.5rem;
border:1px solid #BFC8CD;
text-align: center;
font-weight: normal;
}
.i18n dt a span {
display:block;
padding:5px;
}
.i18n dd ul {
box-shadow: 1px 1px 4px #9EABB3;
border-radius:3px;
background:rgb(245, 248, 250) none repeat scroll 0 0;
display:none;
font-size: 1.5rem;
list-style:none;
padding:0px;
position:absolute;
left:0px;
min-width:100px;
z-index: 200;
}
.i18n dd ul li:first-child {
box-shadow: 0px 1px 0px #9EABB3;
}
.i18n span.value {
display:none;
}
.i18n dd ul li a {
padding:5px;
display:block;
}
@media (max-width: 500px) {
.i18n dt a {
border:none;
}
}
@media (max-width: 960px) {
dl.i18n {
float:left;
}
.main-header {
overflow:auto;
}
}
@media (min-width: 960px) {
dl.i18n {
float:right;
}
.main-header {
overflow:auto;
}
}
Ya tenemos el botón y los estilos, lo que nos faltaría sería agregar la funcionalidad y es lo que haremos a continuación, necesitaremos modificar los archivos default.hbs
que se encuentra en content/themes/casper/default.hbs
y index.js
ubicado en content/themes/casper/assets/js/index.js
.
El primer archivo a modificar sera default.hbs
donde agregaremos justo antes de la etiqueta de cierre </body>
y después de los scripts:
Version para español - es/content/themes/casper/default.hbs
{{!-- Script for i18n --}}
<script type="text/javascript">
$(document).ready(function () {
var i18nEnglishUrl = window.location.href.replace('{{url}}', i18nEnglishKeys['{{url}}']);
$('.i18n dd ul li > a[href="#"]').attr("href", i18nEnglishUrl);
});
</script>
Version para ingles - en/content/themes/casper/default.hbs
{{!-- Script for i18n --}}
<script type="text/javascript">
$(document).ready(function () {
var i18nEnglishUrl = window.location.href.replace('{{url}}', i18nSpanishKeys['{{url}}']);
$('.i18n dd ul li > a[href="#"]').attr("href", i18nEnglishUrl);
});
</script>
Y justo al inicio del archivo index.js
vamos a agregar:
Version para español - es/content/themes/casper/assets/js/index.js
// i18n keys
var i18nEnglishKeys = {
'/es/blog/': '/en/blog/',
'/es/blog/bienvenido-a-ghost/': '/en/blog/welcome-to-ghost/'
};
$(document).ready(function() {
// i18n button
$(".i18n dt a").click(function() {
$(".i18n dd ul").toggle();
});
$(document).bind('click', function(e) {
var $clicked = $(e.target);
if (! $clicked.parents().hasClass("i18n"))
$(".i18n dd ul").hide();
});
});
Version para ingles - en/content/themes/casper/assets/js/index.js
// i18n keys
var i18nSpanishKeys = {
'/en/blog/': '/es/blog/',
'/en/blog/welcome-to-ghost/': '/es/blog/bienvenido-a-ghost/'
};
$(document).ready(function() {
// i18n button
$(".i18n dt a").click(function() {
$(".i18n dd ul").toggle();
});
$(document).bind('click', function(e) {
var $clicked = $(e.target);
if (! $clicked.parents().hasClass("i18n"))
$(".i18n dd ul").hide();
});
});
Modificar el archivo de configuración de Nginx
Vamos a modificar el archivo que creamos en el paso Instalar Nginx, para que nuestro servidor apunte a nuestras dos instancias Ghost.
sudo nano /etc/nginx/sites-available/ghost
Eliminamos el contenido que tiene el archivo y pegamos lo siguiente:
server {
listen 80;
server_name localhost;
location / {
rewrite ^ http://my_domain_name_or_server_ip/en/blog/ permanent;
}
location /en/blog/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_pass http://localhost:2368;
}
location /es/blog/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_pass http://localhost:2369;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
Ten en cuenta que donde dice nombre_de_mi_pagina_o_ip_de_servidor
deberás colocar tanto el nombre de tu página o la ip del servidor en la que estes instalando Ghost.
Guarda el archivo y sal del editor de texto nano presionando CTRL + X
luego escribes Y
y finalmente presionas ENTER.
Para que Nginx aplique los cambios realizados debemos ejecutar el siguiente comando:
sudo service nginx restart
Encender ambas instancias de Ghost
Vamos a situarnos en ambas instancias para encender los servicios de ghost:
cd /var/www/ghost/en/
npm start --production &
cd /var/www/ghost/es/
npm start --production &
De esa forma ya tenemos nuestras dos instancias de Ghost, para Ingles y Español corriendo en nuestro servidor.
Cambiar la ruta del post por defecto en nuestra instancia de Ghost para Español
Vamos al navegador y colocamos la ruta que le asignamos a nuestra instancia de ghost:
http://nombre_de_mi_pagina_o_ip_de_servidor/es/blog/
Podremos ver que satisfactoriamente nuestra instancia para español esta funcionando...
Ahora vamos a dirigirnos a la sección de administración "admin", que sería:
http://nombre_de_mi_pagina_o_ip_de_servidor/es/blog/admin
Allí veremos la configuración inicial de Ghost para establecer las credenciales de acceso, procedemos a crear nuestra cuenta para luego continuar a la página del panel de control.
Hacemos click en el post que esta por defecto: "Welcome to Ghost", y luego hacemos click en el icono de tuerca que se encuentra en la esquina superior derecha, la cual nos permitira cambiarle la url de nuestro primer post. En el campo "Post URL" colocaremos: bienvenido-a-ghost
.
De esta forma ya tenemos configurado nuestro primer post, para que funcione con i18n :)
A medida que creemos más posts debemos ir registrando cada una de sus respectivas URLS en los objetos que contienen las claves de nuestras urls en ambos idiomas. De esta forma la funcionalidad de i18n que agregamos tendrá un mapa de urls disponibles y sabrá cual URL en ingles corresponde a cual URL en español y viceversa.
Ejemplo, supongamos que este post que están leyendo es nuevo
y como URL le hemos asignado adding-internationalization-i18n-to-ghosts-posts
para Ingles y agregando-internacionalizacion-i18n-a-los-posts-en-ghost
para Español, entonces debemos agregarlo al mapa de las URLS que ya tenemos:
Directorio - es/content/themes/casper/assets/js/index.js
// i18n keys
var i18nEnglishKeys = {
'/es/blog/': '/en/blog/',
'/es/blog/bienvenido-a-ghost/': '/en/blog/welcome-to-ghost/',
'agregando-internacionalizacion-i18n-a-los-posts-en-ghost': 'adding-internationalization-i18n-to-ghosts-posts'
};
Directorio - en/content/themes/casper/assets/js/index.js
// i18n keys
var i18nSpanishKeys = {
'/en/blog/': '/es/blog/',
'/en/blog/welcome-to-ghost/': '/es/blog/bienvenido-a-ghost/',
'adding-internationalization-i18n-to-ghosts-posts': 'agregando-internacionalizacion-i18n-a-los-posts-en-ghost'
};
Ya con eso tendríamos registradas las 2 nuevas URLS en ambos idiomas e instancias.
Eso sería todo por este post, espero que les sea de mucha ayuda y/o utilidad para tener sus post de Ghost en ambos idiomas, cualquier duda, consulta adicional, opinión o cosas en general que piensan que debería mejorar, no duden en comentarlo.
Saludos y un abrazo asíncrono.