login=Login
welcome=Welcome
Descargar el script ../scripts/file/ExcelI18n.groovy
03 March 2018
La mayoría de las aplicaciones Java utilizan la funcionalidad que ofrece para el manejo de mensajes internacionalizados el cual se basa en una serie de ficheros properties que comparten un nombre común más un sufijo que indica el idioma al que corresponden las traducciones incluidas en el mismo.
Por ejemplo, supongamos que nuestra aplicación va a mostrar por defecto los mensajes en inglés pero necesitamos poder mostrarlos también en español y francés. Este caso en Java se corresponde con estos ficheros:
login=Login
welcome=Welcome
login=Identificacion
welcome=Bienvenido
login=Identifier
welcome=Bienvenue
Cuando la aplicación crece, el número de mensajes a mostrar suele hacerlo también y nos vamos centrando en el fichero por defecto hasta tener la mayor cantidad posible de identificadores. Si en este momento queremos realizar la traducción (o encargarsela a alguien) nos encontramos con una serie de ficheros incompletos e inconexos y aunque existen herramientas para facilitar la edición, estas no suelen ser del agrado de quien tiene que traducirlos.
Mediante este script vamos a poder realizar dos procesos diferentes aunque relacionados:
Partiendo de un conjunto de ficheros properties de traducciones incompletas crearemos un fichero Excel donde cada columna corresponderá a un idioma y cada fila a un elemento a traducir en cada idioma
Partiendo de un excel con las traducciones completas crearemos un conjunto de ficheros properties cada uno con las traducciones que le correspondan.
Note
|
Vamos a usar Apache POI para ambas situaciones pero para la escritura vamos a usar el DSL Groovy Excel Builder de James Kleeh para demostrar lo fácil que es escribir un excel con Groovy. |
De forma general vamos a usar las librerías de Apache POI para leer y escribir el Excel, pero como el DSL que vamos a usar las incluye en sus dependencias podemos indicar simplemente este en Grappe:
@Grab('com.jameskleeh:excel-builder:0.4.2')
import org.apache.poi.ss.usermodel.*
import org.apache.poi.hssf.usermodel.*
import org.apache.poi.xssf.usermodel.*
import org.apache.poi.ss.util.*
import org.apache.poi.ss.usermodel.*
import com.jameskleeh.excel.ExcelBuilder
Como hemos dicho el script podrá ejecutar dos acciones diferentes por lo que preparamos un CliBuilder que nos permita interpretar la línea de comandos y los argumentos proporcionados por el usuario:
def cli = new CliBuilder(usage: 'groovy ExcelI18n.groovy -[h] -action generateProperties/generateExcel filename.xls ')
cli.with { // (1)
h(longOpt: 'help', 'Import/Export excel file to i18n properties files.', required: false)
action('generateProperties generateExcel', required: true, args:1, argName:'action')
}
def options = cli.parse(args)
if (!options){
return
}
if(options.h || options.arguments().size()==0) {
cli.usage()
return
}
if( options.action == 'generateProperties'){ //(2)
return generateProperties(options.arguments()[0])
}
if( options.action == 'generateExcel'){ //(3)
return generateExcel(options.arguments()[0])
}
cli.usage() //(4)
preparamos las opciones disponibles
de Excel a properties
de properties a Excel
si no hay acción correcta mostramos el uso
Partiendo de una situación en la que tenemos un conjunto de traducciones incompletas lo que pretendemos hacer es cargar todos estos ficheros en un Excel organizando por filas y columnas los códigos y los idiomas respectivamente.
Para determinar los idiomas que queremos manejar en nuestra aplicación hay que fijarse en que todos ellos siguen el patrón:
filename (_ i18code)? .properties , es decir un nombre de fichero común, un guión bajo y un código de idioma (por ejemplo es, fr, ca, etc) opcionales y una extension fija .properties
void generateExcel(filename){
File excelFile = new File(filename)
String dir = excelFile.absolutePath.split(File.separator).dropRight(1).join(File.separator)
String name = excelFile.name.split('\\.').dropRight(1).join('.')
Properties defaultProperties = new Properties()
defaultProperties.load( new File("${name}.properties").newInputStream() ) //(1)
Map<String,Properties> propertiesMap = [:] //(2)
new File(dir).eachFileMatch ~"${name}(_[A-Za-z]+)\\.properties", { //(3)
def matcher = (it.name =~ "${name}(_[A-Za-z]+)\\.properties" )
String lang = matcher[0][1]?.substring(1)
Properties prp = new Properties()
prp.load( it.newInputStream() )
propertiesMap[ lang ] = prp //(4)
}
ExcelBuilder.output(new FileOutputStream(new File("${filename}"))) {
sheet {
row{
cell("Code")
cell("Default")
propertiesMap.keySet().each { lang ->
cell(lang.toUpperCase())
}
}
defaultProperties.propertyNames().each{ String property-> //(5)
row{
cell(property)
cell(defaultProperties[property])
propertiesMap.keySet().each { lang ->
cell(propertiesMap[lang][property])
}
}
}
}
}
}
cargar en un Properties el fichero por defecto (el cual contienen todos las keys a traducir)
preparar un Map<String,Properties>
buscar los ficheros de traducciones particulares que cumplen el patrón explicado
cargar en el mapa el properties identificado por su código de idioma
crear un Excel donde cada key será una fila y cada idioma volcará su texto
Una vez completado el Excel con las traducciones adecuadas necesitarremos volver a reescribir los ficheros properties
void generateProperties(filename){
File excelFile = new File(filename)
String dir = excelFile.absolutePath.split(File.separator).dropRight(1).join(File.separator)
String name = excelFile.name.split('\\.').dropRight(1).join('.')
InputStream inp = new FileInputStream(excelFile)
//(1)
new File(dir).eachFileMatch ~"${name}(_[A-Za-z]+)?\\.properties", {
it.delete()
}
List<String> languages = []
Workbook wb = WorkbookFactory.create(inp)
Sheet sheet = wb.getSheetAt(0)
sheet.iterator().eachWithIndex{ Row row, int idx-> //(2)
if( idx == 0){ //(3)
languages = ['']+row.cellIterator().collect{ '_'+it.stringCellValue }.drop(2)*.toLowerCase()
return
}
String code = row.getCell(0)
languages.eachWithIndex{ String lang, int i -> //(4)
String txt = row.getCell(i+1)?.stringCellValue
if(txt)
new File("${name}${lang}.properties") << "$code=$txt\n" //(5)
}
}
}
limpiamos ficheros de traduccion si los hubiera
iteramos por las filas del Excel
la primera fila nos indica los lenguajes que se contemplan además del por defecto
para cada lenguaje indicado en la primera fila buscamos si hay traducción en el excel
si tenemos traducción la concatenamos al fichero correspondiente al idioma
//tag::dependencies[]
@Grab('com.jameskleeh:excel-builder:0.4.2')
import org.apache.poi.ss.usermodel.*
import org.apache.poi.hssf.usermodel.*
import org.apache.poi.xssf.usermodel.*
import org.apache.poi.ss.util.*
import org.apache.poi.ss.usermodel.*
import com.jameskleeh.excel.ExcelBuilder
//end::dependencies[]
//tag::arguments[]
def cli = new CliBuilder(usage: 'groovy ExcelI18n.groovy -[h] -action generateProperties/generateExcel filename.xls ')
cli.with { // (1)
h(longOpt: 'help', 'Import/Export excel file to i18n properties files.', required: false)
action('generateProperties generateExcel', required: true, args:1, argName:'action')
}
def options = cli.parse(args)
if (!options){
return
}
if(options.h || options.arguments().size()==0) {
cli.usage()
return
}
if( options.action == 'generateProperties'){ //(2)
return generateProperties(options.arguments()[0])
}
if( options.action == 'generateExcel'){ //(3)
return generateExcel(options.arguments()[0])
}
cli.usage() //(4)
//end::arguments[]
//tag::generateProperties[]
void generateProperties(filename){
File excelFile = new File(filename)
String dir = excelFile.absolutePath.split(File.separator).dropRight(1).join(File.separator)
String name = excelFile.name.split('\\.').dropRight(1).join('.')
InputStream inp = new FileInputStream(excelFile)
//(1)
new File(dir).eachFileMatch ~"${name}(_[A-Za-z]+)?\\.properties", {
it.delete()
}
List<String> languages = []
Workbook wb = WorkbookFactory.create(inp)
Sheet sheet = wb.getSheetAt(0)
sheet.iterator().eachWithIndex{ Row row, int idx-> //(2)
if( idx == 0){ //(3)
languages = ['']+row.cellIterator().collect{ '_'+it.stringCellValue }.drop(2)*.toLowerCase()
return
}
String code = row.getCell(0)
languages.eachWithIndex{ String lang, int i -> //(4)
String txt = row.getCell(i+1)?.stringCellValue
if(txt)
new File("${name}${lang}.properties") << "$code=$txt\n" //(5)
}
}
}
//end::generateProperties[]
//tag::generateExcel[]
void generateExcel(filename){
File excelFile = new File(filename)
String dir = excelFile.absolutePath.split(File.separator).dropRight(1).join(File.separator)
String name = excelFile.name.split('\\.').dropRight(1).join('.')
Properties defaultProperties = new Properties()
defaultProperties.load( new File("${name}.properties").newInputStream() ) //(1)
Map<String,Properties> propertiesMap = [:] //(2)
new File(dir).eachFileMatch ~"${name}(_[A-Za-z]+)\\.properties", { //(3)
def matcher = (it.name =~ "${name}(_[A-Za-z]+)\\.properties" )
String lang = matcher[0][1]?.substring(1)
Properties prp = new Properties()
prp.load( it.newInputStream() )
propertiesMap[ lang ] = prp //(4)
}
ExcelBuilder.output(new FileOutputStream(new File("${filename}"))) {
sheet {
row{
cell("Code")
cell("Default")
propertiesMap.keySet().each { lang ->
cell(lang.toUpperCase())
}
}
defaultProperties.propertyNames().each{ String property-> //(5)
row{
cell(property)
cell(defaultProperties[property])
propertiesMap.keySet().each { lang ->
cell(propertiesMap[lang][property])
}
}
}
}
}
}
//end::generateExcel[]