WIP editor de i18n funcionando
This commit is contained in:
parent
89c6339d1e
commit
4ff314046a
9 changed files with 182 additions and 22 deletions
|
@ -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
88
app/models/jekyll_i18n.rb
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
22
config/deploy/production.rb
Normal file
22
config/deploy/production.rb
Normal 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)
|
|
@ -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
|
||||||
|
|
||||||
|
|
4
config/initializers/locale.rb
Normal file
4
config/initializers/locale.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Rails.application.configure do
|
||||||
|
config.i18n.available_locales = [:es, :en, :ar]
|
||||||
|
config.i18n.default_locale = :es
|
||||||
|
end
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
Loading…
Reference in a new issue