Saltar al contenido
Development

Modo de traducción para blog-manager

Por Victor Da Luz
railsrubyi18ndev-logblog-manager

Llevo un tiempo trayendo poco a poco la traducción al español a vdaluz.com, una entrada a la vez, a mano: copiar el markdown en inglés, traducirlo, pegarlo en un archivo nuevo bajo src/content/blog/es/, hacer commit. Funciona, pero es lo bastante tedioso como para que lo siguiera postergando. Este issue se trataba de darle a blog-manager un editor de verdad para eso en lugar de cirugía cruda de archivos.

La forma del problema es distinta a la del editor en inglés que blog-manager ya tiene. Las entradas en inglés son la fuente de verdad, un archivo, un conjunto de campos, commit y listo. Una traducción es un segundo archivo que comparte parte de la metadata de esa entrada (pubDate, category, tags, author, hero image) pero tiene su propio title, description y body. Esos campos compartidos no deberían poder editarse de forma independiente en la traducción, si corrijo un error tipográfico en la category de una entrada en inglés, quiero que eso fluya automáticamente hacia la versión en español la próxima vez que la toque, no que dependa de acordarme de editar dos archivos.

Lo que construí

Una tabla post_translations con una fila por cada entrada con archivo en español, que registra dos shas: file_sha (el commit actual del archivo en es/) y source_file_sha (el sha del archivo en inglés al momento del último commit de traducción). Comparar ambos da un status, :unverified si nunca se hizo commit, :stale si el inglés cambió desde entonces, :present si están sincronizados. Una entrada sin fila alguna es :missing. El invariante que quería: existe una fila si y solo si el archivo es/ de esa entrada existe actualmente en GitHub, así que el scanner reconcilia esto en cada escaneo, no solo al momento del commit.

El editor en sí es una vista dividida, el inglés a la izquierda como referencia de solo lectura (alternador raw/rendered, reutilizando el renderizador de vista previa existente), el borrador en español a la derecha usando la misma maquinaria de autoguardado/draft-frame que ya tiene el editor en inglés. Los campos compartidos aparecen como inputs ocultos que reflejan los valores en inglés, únicamente para que las búsquedas de target del controller de draft existente no fallen, nunca se leen de vuelta hacia el commit.

Decisiones que tomé y por qué

Reduje el alcance de esto a mitad de la planificación. El issue original también quería un aviso del tipo “se modificó un campo compartido en la edición en inglés, ¿llevar eso también a la traducción?”, un empujón proactivo de sincronización. Separé eso en un follow-up en lugar de construirlo ahora, porque es un problema de UX genuinamente distinto (cuándo conviene avisar, qué pasa si algún día hay varias traducciones) y no quería bloquear el flujo de edición principal por resolver bien eso. Alcance principal: abrir una traducción, editarla, hacerle commit, ver su status. Eso es todo.

También traté author como un campo compartido en lugar de algo que quien traduce pudiera sobrescribir. Mi blog solo tiene un autor (yo), así que no había un caso real para atribución por locale, y tratarlo como compartido mantiene más simple la idea de que “las traducciones no son dueñas de la metadata”.

Lo que me sorprendió / no funcionó

El bug interesante no estaba en el código nuevo, era un bug que ya conocía del trabajo del editor y que copié sin querer. Frontmatter#update! en este código base no combina un valor dentro de un nodo YAML existente, reconstruye el nodo, lo que reinicia su estilo de emisión. Si el campo tags de una entrada está escrito como una lista de bloque YAML y se llama a update!("tags" => same_array), vuelve como un array de estilo flow aunque el valor nunca haya cambiado. El committer en inglés ya tiene una protección para esto (comparar primero, pasar solo lo que realmente es distinto). Mi primer intento del refresh_shared_fields del committer de traducciones no tenía esa protección, así que cada commit de traducción habría reformateado en silencio los tags y el bloque de crédito de hero del archivo es/, hubiera cambiado el inglés o no. La revisión de código lo detectó antes del merge; ninguna prueba lo detectó, porque ningún fixture tenía por casualidad campos compartidos ya sincronizados de entrada. Escribí una nota de knowledge base sobre esto, porque es exactamente el tipo de cosa que algún día va a morder un tercer call site si nadie está atento.

La otra que me agarró: al principio grababa source_file_sha a partir de post.file_sha (el valor cacheado de la base de datos) en lugar del sha del archivo en inglés que acababa de obtener fresco en la misma request. Si el blog no había sido re-escaneado desde el último cambio en inglés, eso es un valor obsoleto escrito en la fila que se supone debe probar frescura, una traducción podía leerse como :stale inmediatamente después de ser actualizada, lo cual anula todo el sentido del status.

También me topé con un simple vacío durante la verificación, no un bug: dev no tiene ningún token de GitHub configurado para este blog, así que esta vez no pude realmente recorrer el flujo de commit en un navegador, solo las pruebas unitarias/de integración ejercitan ese camino. Vale la pena arreglarlo en algún momento, pero no bloquea esto.

Qué sigue

El follow-up de propagación de campos compartidos (llevar un cambio de campo compartido desde el inglés hacia una traducción existente sin requerir una reedición completa de la traducción) está registrado y esperando en el backlog, con prioridad baja. Ningún otro blog tiene todavía un directorio es/, así que todo esto por ahora es un no-op en producción hasta que efectivamente empiece a traducir algo.

Lecturas relacionadas