WIP editor de i18n funcionando

This commit is contained in:
f 2018-02-19 16:33:28 -03:00
parent 89c6339d1e
commit 4ff314046a
No known key found for this signature in database
GPG key ID: F3FDAB97B5F9F7E7
9 changed files with 182 additions and 22 deletions

View file

@ -11,11 +11,22 @@ class I18nController < ApplicationController
@site = find_site @site = find_site
@lang_from = I18n.locale.to_s @lang_from = I18n.locale.to_s
@lang_to = @site.config['i18n'].reject { |i| i == @lang_from }.sample @lang_to = @site.config['i18n'].reject { |i| i == @lang_from }.sample
@lang_to = 'es' @lang_to = 'ar'
end end
def update def update
@site = find_site @site = find_site
binding.pry # No usamos params porque nos obliga a hacer una lista blanca de
# todos los parámetros que queremos, pero no tenemos forma aun de
# pasarse a permit un array de todas las keys y sus tipos en base al
# idioma que ya existe
p = request.parameters[:i18n][:ar]
i = JekyllI18n.new(site: @site, lang: :ar, attributes: p)
if i.save
redirect_to site_path(@site)
else
render 'i18n/edit'
end
end end
end end

88
app/models/jekyll_i18n.rb Normal file
View file

@ -0,0 +1,88 @@
# Esta clase se encarga de guardan los archivos YAML de idiomas
#
# TODO se podría convertir en una clase genérica de editor de YAML
class JekyllI18n
attr_reader :site, :lang, :attributes
def initialize(site:, lang:, attributes:)
unless site.is_a? Site
raise ArgumentError, I18n.t('errors.argument_error', argument: :site, class: Site)
end
unless I18n.available_locales.include? lang.to_sym
raise ArgumentError, I18n.t('errors.unknown_locale', locale: lang)
end
@site = site
@lang = lang.to_sym
@attributes = attributes.to_hash # porque enviamos parametros
end
# Vuelva los datos a YAML y los guarda en el archivo correspondiente
#
# TODO es necesario evitar que se agreguen llaves nuevas? No veo nada
# inseguro...
#
# https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
#
# Pero parece que ya se resolvió hace rato. En general es mala
# práctica aceptar cualquier input de las usuarias, pero en este caso
# parece que no nos tenemos que preocupar?
#
# En cualquier caso habría que hacer un deep_merge, descartando las
# llaves que no están en el original y/o en el destino (pero queremos
# que el destino se parezca al original) y chequeando que no haya
# deserialización de objetos
#
# Jekyll usa SafeYAML para cargar los datos, con lo que estaríamos
# bien en cuanto a inyección de código.
def save
# Reemplaza los datos en el sitio
replace_lang_in_site
# Escribe los cambios en disco
write
end
# Obtiene la ruta a partir del sitio y el idioma
def path
File.join(@site.path, '_data', "#{@lang.to_s}.yml")
end
def exist?
File.exist? path
end
private
def replace_lang_in_site
@site.data[@lang.to_s] = @attributes
end
# Escribe los cambios en disco
#
# TODO unificar con Post.write
def write
r = File.open(path, File::RDWR | File::CREAT, 0o640) do |f|
# Bloquear el archivo para que no sea accedido por otro
# proceso u otra editora
f.flock(File::LOCK_EX)
# Empezar por el principio
f.rewind
# Escribir
f.write(content)
# Eliminar el resto
f.flush
f.truncate(f.pos)
end
return true if r.zero?
false
end
def content
@attributes.to_yaml
end
end

View file

@ -22,9 +22,21 @@ class Site
end end
alias :to_s :id alias :to_s :id
def read
@jekyll.read
end
# Fuerza relectura del sitio, eliminando el sitio actual en favor de
# un sitio nuevo, leído desde cero
def read!
@jekyll = Site.load_jekyll(@jekyll.path)
@jekyll.read
end
def data def data
if @jekyll.data.empty? if @jekyll.data.empty?
@jekyll.read read
Rails.logger.info 'Leyendo data' Rails.logger.info 'Leyendo data'
end end
@ -33,7 +45,7 @@ class Site
def config def config
if @jekyll.config.empty? if @jekyll.config.empty?
@jekyll.read read
Rails.logger.info 'Leyendo config' Rails.logger.info 'Leyendo config'
end end
@ -95,6 +107,17 @@ class Site
File.directory?(dir) && File.exist?(File.join(dir, '_config.yml')) File.directory?(dir) && File.exist?(File.join(dir, '_config.yml'))
end end
def self.load_jekyll(path)
config = ::Jekyll.configuration('source' => path)
# No necesitamos cargar plugins en este momento
%w[plugins gems].each do |unneeded|
config[unneeded] = [] if config.key? unneeded
end
Jekyll::Site.new(config)
end
# Obtener todos los directorios de sitios asociados a esta usuaria # Obtener todos los directorios de sitios asociados a esta usuaria
def self.all_for(usuaria) def self.all_for(usuaria)
@sites ||= Pathname.new(Site.site_path_for(usuaria)) @sites ||= Pathname.new(Site.site_path_for(usuaria))
@ -103,14 +126,8 @@ class Site
next unless Site.jekyll? j next unless Site.jekyll? j
Dir.chdir(j) do Dir.chdir(j) do
config = ::Jekyll.configuration('source' => Dir.pwd) jekyll = Site.load_jekyll(Dir.pwd)
Site.new(jekyll: jekyll, path: j)
# No necesitamos cargar plugins en este momento
%w[plugins gems].each do |unneeded|
config[unneeded] = [] if config.key? unneeded
end
Site.new(jekyll: ::Jekyll::Site.new(config), path: j)
end end
end.compact end.compact
end end

View file

@ -1,15 +1,34 @@
- dir = 'ltr'
- if @lang_to == 'ar'
- dir = 'rtl'
.form-group .form-group
- key = keys.pop - key = keys.pop
- form_keys = keys.map { |k| "[#{k.to_s}]" }.join('') -# si la key es numerica, queremos un array de hashes, no un hash de
-# hashes con keys numericas
- form_keys = keys.map do |k|
- if k.is_a? Integer
- '[]'
- else
- "[#{k.to_s}]"
- form_keys = form_keys.join('')
- form_help = (keys.size > 0) ? [keys,key].flatten.join('.') : key - form_help = (keys.size > 0) ? [keys,key].flatten.join('.') : key
- value_to = @site.data[@lang_to] - value_to = @site.data[@lang_to]
-# recorrer el hash hasta obtener el valor original
- [keys,key].flatten.each do |k| - [keys,key].flatten.each do |k|
- if value_to.nil?
- value_to = ''
- break
- value_to = value_to[k] - value_to = value_to[k]
= label_tag "i18n[#{@lang_to}]#{form_keys}", value -# no especificar el id en una key numerica para que no se genere un
-# hash en lugar de un array de valores
- key = '' if key.is_a? Integer
= label_tag "i18n[#{@lang_to}]#{form_keys}[#{key}]", value
-# creamos un campo a mano porque los helpers de niveles mas altos
-# quieren hacer magia con los ids y fallan
- if value.length > 140 - if value.length > 140
= text_area "i18n[#{@lang_to}]#{form_keys}", key, value: value_to, = text_area_tag "i18n[#{@lang_to}]#{form_keys}[#{key}]", value_to,
class: 'form-control' class: "form-control #{dir}"
- else - else
= text_field "i18n[#{@lang_to}]#{form_keys}", key, value: value_to, = text_field_tag "i18n[#{@lang_to}]#{form_keys}[#{key}]", value_to,
class: 'form-control' class: "form-control #{dir}"
%small.text-muted.form-text= form_help %small.text-muted.form-text= form_help

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
set :deploy_user, 'app'
set :deploy_to, '/srv/http/sutty.kefir.red'
set :branch, 'rails'
set :rack_env, 'production'
set :tmp_dir, "#{fetch :deploy_to}/tmp"
set :bundle_path, '/srv/http/gems.kefir.red'
set :rbenv_type, :user
set :rbenv_ruby, '2.3.6'
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"
set :rbenv_map_bins, %w(rake gem bundle ruby rails)
set :rbenv_roles, :all # default value
# Evitar compilar nokogiri
set :bundle_env_variables, nokogiri_use_system_libraries: 1
server 'miso',
user: fetch(:deploy_user),
roles: %w(app web db)

View file

@ -66,9 +66,6 @@ Rails.application.configure do
# the I18n.default_locale when a translation cannot be found). # the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true config.i18n.fallbacks = true
config.i18n.available_locales = [:es, :en]
config.i18n.default_locale = :es
# Send deprecation notices to registered listeners. # Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify config.active_support.deprecation = :notify

View file

@ -0,0 +1,4 @@
Rails.application.configure do
config.i18n.available_locales = [:es, :en, :ar]
config.i18n.default_locale = :es
end

View file

@ -1,6 +1,7 @@
en: en:
errors: errors:
argument_error: 'Argument `%{argument}` must be an instance of %{class}' argument_error: 'Argument `%{argument}` must be an instance of %{class}'
unknown_locale: 'Unknown %{locale} locale'
login: login:
email: 'E-mail' email: 'E-mail'
password: 'Password' password: 'Password'

View file

@ -1,6 +1,7 @@
es: es:
errors: errors:
argument_error: 'El argumento `%{argument}` debe ser una instancia de %{class}' argument_error: 'El argumento `%{argument}` debe ser una instancia de %{class}'
unknown_locale: 'El idioma %{locale} es desconocido'
login: login:
email: 'Dirección de correo' email: 'Dirección de correo'
password: 'Contraseña' password: 'Contraseña'