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