grails интернационализация (i18n)
Осваиваю grails. Первая проблема - интернационализация. К счастью в grails уже всё для этого готово, единственное что захотелось ускорить - создание ключей для строк всех объектов в проекте.
Плагин который позволяет сгенерировать все ключи для домена называется i18n-templates.
Устанавливаем плагин:
grails install-plugin i18n-templates -global
Допиливаем под grails 2.3: В файле $USER_HOME/.grails/<grailsVersion>/projects/<PROJECTNAME>/plugins/i18n-templates-1.1.0.1/scripts/GenerateI18nMessages.groovy Изменяем процедуру generateForDomainClass (изменения выделены жирным):
def generateForDomainClass(domainClass) {
// print generic messages for this domain class
println "# ${domainClass.shortName} messages"
println "${domainClass.shortName}.label=${domainClass.shortName}"
println "${domainClass.propertyName}.create.label=Create ${domainClass.shortName}"
println "${domainClass.propertyName}.edit.label=Edit ${domainClass.shortName}"
println "${domainClass.propertyName}.list.label=${domainClass.shortName} List"
println "${domainClass.propertyName}.new.label=New ${domainClass.shortName}"
println "${domainClass.propertyName}.show.label=Show ${domainClass.shortName}"
println "${domainClass.propertyName}.created.label=${domainClass.shortName} {0} created"
println "${domainClass.propertyName}.updated.label=${domainClass.shortName} {0} updated"
println "${domainClass.propertyName}.deleted.label=${domainClass.shortName} {0} deleted"
println "${domainClass.propertyName}.not.found.label=${domainClass.shortName} not found with id {0}"
println "${domainClass.propertyName}.not.deleted.label=${domainClass.shortName} not deleted with id {0}"
println "${domainClass.propertyName}.optimistic.locking.failure.label=Another user has updated this ${domainClass.shortName} while you were editing"
// print messages for all properties contained by domain class
props = domainClass.properties.findAll { it.name != 'version' }
Collections.sort(props, new DomainClassPropertyComparator(domainClass))
props.each { p ->
println "${domainClass.propertyName}.${p.name}.label=${p.naturalName}"
// print messages for inList constaint values
cp = domainClass.constrainedProperties[p.name]
if (cp?.inList) {
cp.inList.each { v ->
println "${domainClass.propertyName}.${p.name}.${v}.label=${v}"
}
}
// print error messages for constraints
cp?.appliedConstraints?.each { c ->
switch (c.name) {
case ConstrainedProperty.BLANK_CONSTRAINT:
if (!c.parameter)
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] cannot be blank"
break
case ConstrainedProperty.CREDIT_CARD_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] is not a valid credit card number"
break
case ConstrainedProperty.EMAIL_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] is not a valid e-mail address"
break
case ConstrainedProperty.IN_LIST_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] is not contained within the list [{3}]"
break
case ConstrainedProperty.MATCHES_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] does not match the required pattern [{3}]"
break
case ConstrainedProperty.MAX_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] exceeds maximum value [{3}]"
break
case ConstrainedProperty.MAX_SIZE_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] exceeds the maximum size of [{3}]"
break
case ConstrainedProperty.MIN_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] is less than minimum value [{3}]"
break
case ConstrainedProperty.MIN_SIZE_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] is less than the minimum size of [{3}]"
break
case ConstrainedProperty.NOT_EQUAL_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] cannot equal [{3}]"
break
case ConstrainedProperty.NULLABLE_CONSTRAINT:
if (!c.nullable)
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] cannot be null"
break
case ConstrainedProperty.RANGE_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] does not fall within the valid range from [{3}] to [{4}]"
break
case ConstrainedProperty.SIZE_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}]"
break
//case ConstrainedProperty.UNIQUE_CONSTRAINT: // unique constraint reference not available
// println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] must be unique"
// break
case ConstrainedProperty.URL_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] is not a valid URL"
break
case ConstrainedProperty.VALIDATOR_CONSTRAINT:
println "${domainClass.propertyName}.${p.name}.${c.name}.error=Property [${p.naturalName}] of class [${domainClass.shortName}] with value [{2}] does not pass custom validation"
break
}
}
}
println ""
}
Запускаем для домена:
grails generate-i18n-messages <domainClass>
или для всех (что ещё проще):
grails generate-i18n-messages *
В окне вывода получаем готовые строки для переноса их в messages.properties
Часть фраз не переведено по умолчанию, их лучше сразу добавить в messages_ru.properties
default.list.label=Список {0}
default.create.label=Создать {0}
default.link.skip.label=Пропустить
default.home.label=Главная
default.edit.label=Редактировать {0}
default.new.label=Новый {0}
default.show.label=Показать {0}
default.button.create.label=Создать
default.button.edit.label=Редактировать
default.button.update.label=Сохранить
default.button.delete.label=Удалить
Если необходимо установить по умолчанию русский язык, то добавляем в conf/spring/resources.groovy:
beans = {
localeResolver(org.springframework.web.servlet.i18n.SessionLocaleResolver) {
defaultLocale = new Locale("ru","RU")
java.util.Locale.setDefault(defaultLocale)
}
}
Ссылки:
http://grails.org/plugin/i18n-templates
Интернационализация javascript: http://mrhaki.blogspot.ru/2011/11/grails-goodness-internationalize.html
