Текст книги "Программирование на языке Ruby"
Автор книги: Хэл Фултон
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 14 (всего у книги 56 страниц) [доступный отрывок для чтения: 20 страниц]
До сих пор мы пользовались монолитными символами, в которых базовый символ и диакритический знак объединены в одну кодовую позицию. Но, вообще говоря, в Unicode символы и диакритические знаки представлены отдельно. Вместо того чтобы хранить букву é в кодовой позиции СТРОЧНАЯ ЛАТИНСКАЯ БУКВА E С АКУТОМ, можно было бы представить ее в составной форме как СТРОЧНУЮ ЛАТИНСКУЮ БУКВУ E и МОДИФИЦИРУЮЩИЙ АКУТ.
Для чего это может понадобиться? Для обеспечения дополнительной гибкости и возможности применять диакритические знаки к любому символу, а не ограничивать себя комбинациями, которые предусмотрел проектировщик кодировки. На самом деле в шрифты включены глифы для наиболее распространенных комбинаций символа и диакритического знака, но отображение символа и его кодирование – вещи разные.
При проектировании Unicode приходилось учитывать такие вещи, как эффективность и совместимость с существующими национальными кодировками. Иногда это приводит к избыточности; например, в Unicode имеются кодовые позиции как для составных форм, так и для многих уже применяющихся монолитных форм.
Рассмотрим, к примеру, немецкое слово «öffnen» (открывать). Даже если забыть о регистре, его можно закодировать четырьмя способами:
1. о
+ МОДИФИЦИРУЮЩАЯ ТРЕМА (u+0308
) +f+f+n+e+n
2. СТРОЧНАЯ ЛАТИНСКАЯ БУКВА О С ТРЕМОЙ (U+00F6
) + f + f + n + е + n
3. о + МОДИФИЦИРУЮЩАЯ ТРЕМА + ЛИГАТУРА ДВОЙНОЕ F (U+FB00
) + n + е + n
.
4. СТРОЧНАЯ ЛАТИНСКАЯ БУКВА О С ТРЕМОЙ + ЛИГАТУРА ДВОЙНОЕ F + n + e + n
Трема – это две точки над буквой (в немецком языке называется «умляут»).
Нормализацией называется процедура приведения разных представлений символа к стандартной форме. Можно быть уверенным, что после нормализации данный символ закодирован вполне определенным образом. Каким именно, зависит оттого, чего мы хотим достичь. В приложении 15 к стандарту Unicode перечислены четыре формы нормализации:
1. Форма D (каноническая декомпозиция).
2. Форма С (каноническая декомпозиция с последующей канонической композицией).
3. Форма KD (совместимая декомпозиция).
4. Форма KC (совместимая декомпозиция с последующей канонической композицией).
Иногда можно встретить аббревиатуры NKFC (Normalization Form KC) и т.д.
Точные правила, сформулированные в стандарте, довольно сложны; в них проведено различие между «канонической эквивалентностью» и «совместимой эквивалентностью». (Корейский и японский языки требуют особого рассмотрения, но мы не станем тратить на это время.) В таблице 4.2 показано, как форма нормализации влияет на приведенные выше строки.
Таблица 4.2. Нормализованные формы в Unicode
o+ ̈+f+f+n+e+n | o+ ̈+f+f+n+e+n | ö+f+f+n+e+n | o+ ̈+f+f+n+e+n | ö+f+f+n+e+n |
ö+f+f+n+e+n | o+ ̈+f+f+n+e+n | ö+f+f+n+e+n | o+ ̈+f+f+n+e+n | ö+f+f+n+e+n |
o+ ̈+ff+n+e+n | o+ ̈+ff+n+e+n | ö+ff+n+e+n | o+ ̈+f+f+n+e+n | ö+f+f+n+e+n |
ö+ff+n+e+n | o+ ̈+ff+n+e+n | ö+ff+n+e+n | o+ ̈+f+f+n+e+n | ö+f+f+n+e+n |
Формы С и D обратимы, KC и KD – нет. С другой стороны, потеря некоторых данных в формах KC и KD – свидетельство того, что все четыре строки двоично эквивалентны. Какая форма лучше всего подходит, зависит от приложения. Мы ещё вернемся к этой теме в следующем разделе.
Для Ruby есть библиотека, позволяющая выполнить описанные нормализации, хотя в стандартный дистрибутив она не входит. Вы можете скачать ее со страницы http://www.yoshidam.net/Ruby.html и установить командой gem install Unicode
.
Если библиотека Unicode установлена, то для выполнения любой нормализации достаточно вызвать один из методов Unicode.normalize_x
:
require 'Unicode'
sword_kd = Unicode.normalize_KD(sword)
sword_kd.scan(/./) # ["e", "'", "p", "e", "'", "e"]
sword_kc = Unicode.normalize_KC(sword)
sword_kc.scan(/./) # [ "é", "p", "é", "e"]
Обычно, хотя и не всегда, строки упорядочиваются по алфавиту или сходным образом. Упорядочение тесно связано с нормализацией: в обоих случаях применяются одни и те же идеи и библиотеки.
Предположим, например, что мы хотим отсортировать такой массив строк:
eacute = [0x00Е9].pack('U')
acute = [0x0301].pack('U')
array = ["epicurian", "#{eacute}p#{eacute}e", "e#{acute}lan"]
# ["epicurian", "éрéе", "élan"]
Что произойдет, если передать этот массив методу Array#sort
?
array.sort # ["epicurian", "élan", "éрéе"]
He годится!.. Попытаемся понять, почему так получилось. Сортируемые строки Ruby сравнивает побайтно. Чтобы убедиться в этом, достаточно взглянуть на первые несколько байтов каждой строки:
array.map {|item| "#{item}: #{item.unpack('С*')[0,3].join(',')}" }
# ["epicurian: 101,112,105", "éрéе: 195,169,112",
# "élan: 101,204,129"]
Тут возникают две трудности. Во-первых, символы UTF-8, не имеющие аналога в кодировке ASCII, начинаются с байта, имеющего большое числовое значение, а стало быть, после сортировки неизбежно окажутся после ASCII-символов. Во-вторых, составные латинские символы оказываются раньше монолитных из-за первого ASCII-байта.
В системные библиотеки обычно включают функции сортировки, которые сравнивают строки в соответствии с правилами конкретного языка. В библиотеке, поставляемой вместе с компилятором языка С, для этого служат функции strxfrm
и strcoll
.
Имейте в виду, что проблема возникает даже в случае кодировки ASCII. При сортировке ASCII-строк в Ruby производится прямое лексикографическое сравнение, однако в реальной жизни (например, если мы хотим отсортировать по названиям книги из библиотеки Конгресса США) есть много правил, которые не учитываются при таком упрощенном подходе.
Для упорядочения строк можно создать промежуточные строки и отсортировать именно их. Как конкретно это сделать, зависит от предъявляемых требований и языка; универсального алгоритма не существует.
Предположим, что список обрабатывается согласно правилам английского языка, причем диакритические знаки игнорируются. Первым делом нужно определить методику трансформации. Мы приведем все символы к составному виду, а затем исключим диакритические знаки, оставив только базовые символы. Для модифицирующих диакритических знаков в Unicode выделен диапазон от U+0300
to U+036F
:
def transform(str)
Unicode.normalize_KD(str).unpack('U*').select{ |cp|
cp < 0x0300 || cp > 0x036F
}.pack('U*')
end
array.map{|x| transform(x) } # ["epicurian", "epee", "elan"]
Затем создадим хэшированную таблицу, чтобы установить соответствие между исходными и трансформированными строками, и воспользуемся ей для сортировки исходных строк. Наличие такой таблицы позволяет провести трансформацию только один раз.
def collate(array)
transformations = array.inject({}) do |hash, item|
hash[item] = yield item
hash
end
array.sort_by {|x| transformations[x] }
end
collate(array) {|a| transform(a) } # ["élan", "épée", "epicurian"]
Уже лучше, но мы еще не учли прописные буквы и эквивалентность символов. Возьмем для примера немецкий язык.
На самом деле в немецком языке есть несколько способов упорядочения; мы остановимся на стандарте DIN-2 (как в телефонном справочнике). Согласно этому стандарту, символ ß (эсцет) эквивалентен ss, а умляут эквивалентен букве е (то есть ö – то же самое, что ое и т.д.).
Наш метод трансформации должен учитывать эти детали. Снова начнем с декомпозиции составных символов. Например, модифицирующая трема (умляут) представляется кодовой позицией U+0308
. За основу мы возьмем метод преобразования регистра, имеющийся в Ruby, но несколько дополним его. Вот как выглядит теперь код трансформации:
def transform_de(str)
decomposed = Unicode.normalize_KD(str).downcase
decomposed.gsub!('ß', 'ss')
decomposed.gsub([0x0308].pack('U'), 'e')
end
array = ["Straße", "öffnen"]
array.map {|x| transform_de(x) } # ["strasse", "oeffnen"]
He для всех языков годится такой прямолинейный подход. Например, в испанском между буквами n и о есть еще буква ñ. Однако, если каким-то образом сдвинуть оставшиеся буквы, то мы справимся и с этой проблемой. В листинге 4.1 для упрощения обработки нормализация применена к монолитным символам. Кроме того, мы облегчили себе жизнь, игнорируя различия между буквами с диакритическими знаками и без них.
Листинг 4.1. Упорядочение строк в испанском языке
def map_table(list)
table = {}
list.each_with_index do |item, i|
item.split(',').each do |subitem|
table[Unicode, normalize_KC(subitem)] = (?a + i).chr
end
end
table
end
ES_SORT = map_table(%w(
a,A,á,Á b,B c,C d,D е,Е,é,É f,F g,G h,H i,I,í,Í j,J k,K l,L m,M
n,N ñ,Ñ o,O,ó,Ó p,P q,Q r,R s,S t,T u,U,u,U v,V w,W x,X y,Y z,Z
))
def transform_es(str)
array = Unicode.normalize_KC(str).scan(/./u)
array.map {|c| ES_SORT[c] || c}.join
end
array = %w[éste estoy año apogeo amor]
array.map {|a| transform_es(a) }
# ["etue", "etupz", "aop", "aqpgep", "amps"]
collate(array) {|a| transform_es(a) }
# ["amor", "año", "apogeo", "éste", "estoy"]
В реальности упорядочение немного сложнее, чем показано в примерах выше; обычно требуется до трех уровней обработки. На первом уровне сравниваются только базовые символы без учета диакритических знаков и регистра, на втором учитываются диакритические знаки, а на третьем – регистр. Второй и третий уровень необходимы лишь в том случае, когда на предыдущих уровнях строки совпали. Кроме того, в некоторых языках последовательности, состоящие из нескольких символов, сортируются как единая семантическая единица (например, в хорватском lj расположено между l и m). Поэтому разработка языковозависимого или обобщенного алгоритма сортировки – задача нетривиальная: необходимо хорошо разбираться в конкретном языке. Невозможно изобрести по-настоящему универсальный алгоритм сортировки, который давал бы правильные результаты для всех языков, хотя попытки в этом направлении производились.
4.2.6. Преобразование из одной кодировки в другуюВ стандартной библиотеке Ruby имеется интерфейс к библиотеке iconv
для преобразования из одной кодировки символов в другую. Она должна работать на всех платформах, в том числе и в Windows (если дистрибутив устанавливался моментальным инсталлятором).
Чтобы преобразовать строку из UTF-8 в ISO-8859-15, библиотека iconv
используется следующим образом:
require 'iconv'
converter = Iconv.new('ISO-8859-15', 'UTF-8')
sword_iso = converter.iconv(sword)
Важно помнить, что сначала указывается целевая кодировка, а потом исходная (как при присваивании). Количество и названия поддерживаемых кодировок зависят от платформы, но наиболее распространенные стандартизованы и имеются везде. Если установлена пакетная утилита iconv
, то перечень распознаваемых кодировок можно получить с помощью команды iconv -l
.
Помимо названия кодировки, iconv
принимает еще флаги, управляющие ее поведением. Они указываются в конце строки, содержащей целевую кодировку.
Обычно iconv
возбуждает исключение, если получает недопустимые входные данные или почему-либо не может представить их в целевой кодировке. Флаг //IGNORE
подавляет исключение.
broken_utf8_string = "helloxfe"
converter = Iconv.new('ISO-8859-15', 'UTF-8')
# будет возбуждено исключение Iconv::IllegalSequence
converter.iconv(broken_utf8_string)
converter = Iconv.new('ISO-8859-15//IGNORE', 'UTF-8')
converter.iconv(broken_utf8_string) # "hello"
Этот же флаг позволяет очистить строку от неверных данных:
broken_sword = "épéexfe"
converter = Iconv.new('UTF-8//IGNORE', 'UTF-8')
converter.iconv(broken_sword) # "épée"
Иногда некоторые символы нельзя представить в целевой кодировке. Обычно в этом случае возбуждается исключение. Флаг //TRANSLIT
говорит iconv
, что нужно вместо этого попытаться подобрать приблизительные эквиваленты.
converter = Iconv.new('ASCII', 'UTF-8')
converter.iconv(sword) # Возбуждается Iconv::IllegalSequence.
converter = Iconv.new('ASCII//IGNORE', 'UTF-8')
converter.iconv(sword) # "pe"
converter = Iconv.new('ASCII//TRANSLIT', 'UTF-8')
converter.iconv(sword) # "'ep'ee"
Этим свойством можно воспользоваться, чтобы получить URL, содержащий только ASCII-символы:
str = "Straße épée"
converter = Iconv.new('ASCII//TRANSLIT', 'UTF-8')
converter.iconv(sword).gsub(/ /, '-').gsub(/[^а-z-]/in).downcase
# "strasse-epee"
Однако работать это будет лишь в отношении латиницы. В листинге 4.2 приведен реальный пример совместного применения библиотек iconv
и open-uri
для скачивания Web-страницы и перекодирования ее в UTF-8.
Листинг 4.2. Перекодирование Web-страницы в кодировку UTF-8
require 'open-uri'
require 'iconv'
def get_web_page_as_utf8(url)
open(url) do |io|
source = io.read
type, *parameters = io.content_type_parse
# He перекодировать, если не (X)HTML
unless type =~ %r!^(?:text/html|application/xhtml+xml)$!
return source
end
# Сначала проверяем заголовки, присланные сервером:
if pair = parameters.assoc('charset')
encoding = pair.last
# Затем анализируем HTML:
elsif source =~ ?]*?charset=([^s'"]+)/i
encoding = $1
# Если не удалось определить, предполагаем кодировку по умолчанию,
# определенную в стандарте HTTP.
else
encoding = 'ISO-8859-1'
end
converter = Iconv.new('UTF-8//IGNORE', encoding)
return converter.iconv(source)
end
end
Это еще не все системные вопросы, связанные с преобразованием кодировок. Предположим, что в операционной системе, где установлен Ruby, определена локаль, отличная от UTF-8, или Ruby общается с ОС не в UTF-8 (так, например, обстоит дело в дистрибутиве для Win32). Тогда возникают дополнительные сложности.
Например, Windows поддерживает Unicode в именах файлов и на системном уровне работает исключительно в Unicode. Но в настоящее время Ruby взаимодействует с Windows при помощи устаревших кодовых страниц. Для англоязычного и большинства других западных изданий это страница 1252 (или WINDOWS-1252
).
Внутри программы можно пользоваться и кодировкой UTF-8, но все имена файлов придется перевести в кодировку, заданную кодовой страницей. Iconv поможет это сделать, но важно не забывать, что кодовая страница позволяет описать только малое подмножество всех символов, имеющихся в Unicode.
Кроме того, это означает, что пока Ruby для Windows не может открывать файлы, имена которых нельзя описать с помощью кодовой страницы. Это ограничение не относится к Mac OS X, Linux и другим системам с локалью UTF-8.
4.3. Справочники сообщений
Ложбан не зависит от национальных особенностей. Его словарь был создан алгоритмически на основе шести наиболее распространенных в мире разговорных языков: китайского, хинди, английского, русского, испанского и арабского.
Nick Nicholas, John Cowan. What is Lojban?
Справочник сообщений – это набор сообщений на одном языке. Данное понятие неотъемлемо от концепции локализации (L10N). Идея в том, чтобы отделить языково-зависимые строки от остальной программы. Тогда для того, чтобы программа «заговорила» на другом языке, достаточно всего лишь подменить справочник.
«Наилучший» способ реализовать эту идею в Ruby – воспользоваться библиотекой Ruby-GetText-Package. Я буду называть ее просто gettext
, поскольку именно так называется содержащий ее файл (не путайте с утилитой gettext
!). Эту великолепную библиотеку написал Macao Муто (Masao Mutoh), он же очень помог при написании данного раздела.
Библиотека представляет собой реализацию на Ruby (не обертку) набора утилит gettext
из проекта GNU (самый известный продукт в этой области). Ее официальный сайт находится по адресу http://gettext.rubyforge.org/, а утилиты GNU можно найти на сайте http://www.gnu.org/software/gettext/.
Библиотека gettext
на самом деле, как мы увидим, состоит из нескольких библиотек. Для доступа к основным функциям нужно включить предложение require 'gettext'
, а для получения разного рода дополнительных средств (в частности, работы со справочниками сообщений) – предложение require 'gettext/utils'
.
Главная причина, по которой мы используем справочники сообщений, – это, конечно, перевод сообщений на другие языки. С их помощью мы также обрабатываем случаи, когда формы единственного и множественного числа различаются (один файл, два файла). Кстати, эти правила очень сильно зависят от конкретного языка.
Обычно у каждой библиотеки и приложения имеется собственный справочник сообщений. Следовательно, в дистрибутив можно включать набор переведенных на разные языки справочников.
Учитываются переменные окружения LANG
и GETTEXT_PATH
. Их назначение мы рассмотрим ниже.
Для сопровождения справочника сообщений есть две основных операции (они выполняются вне вашей программы): извлечь сообщения из исходного текста Ruby-программы для формирования начального справочника и включить новые сообщения из исходного текста в существующий справочник (слияние). Операции извлечения и слияния мы рассмотрим в разделе 4.3.3.
4.3.2. Приступаем к работе со справочниками сообщенийВозможно, библиотека gettext
на вашем компьютере уже установлена. Если нет, проще всего выполнить команду gem install gettext
.
Для разработки вам понадобятся утилиты GNU. Если вы работаете в системе UNIX, то, скорее всего, они уже установлены. В случае платформы Win32 можно установить Glade/GTK+ для Windows; заодно вы получите и утилиты GNU. В любом случае необходимы они только на этапе разработки, а не во время выполнения.
Если у вас нет программы rake
, установите ее из gem-пакета. Это дополнительное удобство.
Коль скоро среда настроена и все установлено, можно приступать к работе со справочниками. Но сначала познакомимся с терминологией.
• РО-файл – это переносимый объектный файл. Так называется текстовое (понятное человеку) представление справочника сообщений. У каждого такого файла есть вариант для различных поддерживаемых локалей. РОТ-файл – это шаблон.
• МО-файл – это переносимый двоичный файл справочника. Он создается из РО-файла. Библиотека для Ruby умеет читать только МО-файлы, но не РО-файлы.
• Текстовый домен – это, по существу, просто базовое имя МО-файла. Он ассоциирован с приложением (привязан к нему).
4.3.3. Локализация простого приложенияВ следующем примере определяется класс Person, после чего с ним выполняются различные действия. Метод show выводит локализованные сообщения:
require 'gettext'
class Person
include GetText
def initialize(name, age, children_num)
@name, @age, @children_num = name, age, children_num
bindtextdomain("myapp")
end
def show
puts _("Information")
puts _("Name: %{name}, Age: %{age}") % {:name => @name, :age => @age}
puts n_("%{name} has a child.", "%{name} has %{num} children.",
@children_num) % {:name => @name, :num => @children_num}
end
end
john = Person.new("John", 25, 1)
john.show
linda = Person.new("Linda", 30, 3)
linda.show
Предположим, что этот код сохранен в файле myapp/person.rb
. Как вы скоро увидите, иерархия каталогов имеет значение. Вызов метода bindtextdomain
связывает текстовый домен "myapp"
с объектом Person
во время выполнения.
В методе show
есть три обращения к библиотеке gettext
. Вызываемый метод называется _
(одно подчеркивание), чтобы не отвлекать внимание.
Первое обращение просто выводит локализованное сообщение, соответствующее строке "Information"
. Второе демонстрирует локализованное сообщение с двумя параметрами. В хэше задается список значений, подставляемых в строку. Интерполировать их напрямую нельзя, потому что это вступало бы в противоречие с основной целью: хранить в справочнике небольшое число сообщений.
Отметим также, что параметры отделены от текста сообщения, поэтому при необходимости могут подставляться в другом порядке. Ведь иногда при переводе на другой язык приходится переставлять слова.
Тот же метод можно вызвать и короче:
puts _("Name: %s, Age: %d") % [@name, @age]
Однако мы рекомендуем более длинную запись. Она понятнее и дает больше информации переводчику.
Метод n_
предназначен для обработки единственного и множественного числа. Значение параметра @children_num
– индекс, говорящий о том, какую из заранее заданных строк использовать. (Правило Plural-Forms
, о котором я скоро расскажу, определяет порядок вычисления индекса.)
Отметим, что сообщения по умолчанию обязаны быть англоязычными (даже если родной язык программиста не английский). Нравится вам это или нет, но английский ближе всего к универсальному языку с точки зрения большинства переводчиков.
Я сказал, что нам пригодится программа rake
. Создадим файл Rakefile
(в каталоге myapp
) для сопровождения справочников сообщений. Он будет выполнять две основные операции: обновлять РО-файлы и создавать МО-файлы.
require 'gettext/utils'
desc "Update pot/po files."
task :updatepo do
GetText.update_pofiles("myapp", ["person.rb"], "myapp 1.0.0")
end
desc "Create mo-files"
task :makemo do
GetText.create_mofiles
end
Здесь мы воспользовались библиотекой gettext/utils
, в которой имеются функции для работы со справочниками сообщения. Метод update_pofiles
создает начальный файл myapp/ро/myapp.pot
на основе исходного текста person.rb
. При втором (и всех последующих) вызовах эта функция выполнит обновление, или слияние файла myapp/po/myapp.pot
и всех файлов вида myapp/po/#{lang}/myapp.ро
. Второй параметр – массив целевых файлов. Обычно он задается примерно так:
GetText.update_pofiles("myapp",
Dir.glob("{lib,bin}/**/*.{rb,rhtml}"),
"myapp 1.0.0")
Вызов метода GetText.create_mofiles
создает необходимые подкаталоги в каталоге data/locale/
и генерирует МО-файлы из РО-файлов.
Итак, выполнив команду rake updatepo
, мы создадим каталог myapp/ро
, а в нем файл myapp.pot
.
Теперь отредактируем заголовок файла po/myapp.pot
. Он содержит описание приложения (название, имя автора, адрес электронной почты, условия лицензирования и т.д.).
# Пример приложения. (Осмысленное название)
# Copyright (С) 2006 Foo Bar (Автор приложения)
# Файл распространяется по лицензии XXX. (Лицензия)
#
# FIRST AUTHOR
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: myapp 1.0.0n" (ID и версия проекта)
#...
Что такое маркер fuzzy
? Так отмечается тот факт, что какая-то часть не переведена или перевод вызывает сомнения. Все автоматически сгенерированные сообщения помечаются таким образом, чтобы человек знал, что их нужно проверить и изменить.
Файл myapp.pot
нужно разослать переводчикам. (Конечно, вы можете перевести его и самостоятельно.)
Предположим, что вы переводите на японский язык. На машине установлена локаль ja_jp.UTF-8
, что означает «Япония (ja
), японский язык (JP
), кодировка UTF-8».
Для начала скопируем файл myapp.pot
в myapp.ро
. При наличии набора GNU-утилит gettext
лучше воспользоваться командой msginit
, а не просто cp
. Эта утилита учитывает переменные окружения и правильно устанавливает некоторые переменные в заголовке. В UNIX она вызывается следующим образом:
LANG=ja_JP.UTF-8 msginit -i myapp.pot -o myapp.po
Затем отредактируйте файл myapp.ро
, как показано в листинге 4.3. Редактировать необходимо в той кодировке, которая указана в строке Content-Type
.
Листинг 4.3. Файл myapp.ро после редактирования
# Пример приложения.
# Copyright (С) 2006 Foo Bar
# Файл распространяется по лицензии XXX.
#
# Ваше имя
# (Удалите строку 'fuzzy')
msgid ""
msgstr ""
"Project-Id-Version: myapp 1.0.0n"
"POT-Creation-Date: 2006-05-22 23:27+0900n"
"PO-Revision-Date: 2006-05-23 14:39+0900n"
# Информация о текущем переводчике.
"Last-Translator: Your Name
"Language-Team: Japanesen" (Ваш язык)
"MIME-Version: 1.0n"
"Content-Type: text/plain; charset=UTF-8n" (Кодировка файла)
"Content-Transfer-Encoding: 8bit r."
# Форма множественного числа.
"Plural-Forms: nplurals=2; plural=(n != l);n"
#: person.rb:12
msgid "Information"
msgstr "Jouhou"
#: person.rb:13
msgid "Name: %{name}, Age: %{age}"
msgstr "Namae: %{name}, Nenrei: %{age}"
#: person.rb:14
msgid "%{name} has a child."
msgid_plural "%{name} has %{num} children."
msgstr[0] "%{name} ha hitori kodomo ga imasu."
msgstr[1] "%{name) ha %{num} nin no kodomo ga imasu."
Тегом msgid
помечается исходное сообщение, а тегом msgstr
– переведенное. При наличии строки msgid_plural
необходимо включить отдельные строки msgstr[i]
в соответствии с правилом Plural-Forms
. Индекс i
вычисляется на основе выражения Plural-Forms
. В данном случае при num != 1
используется msgstr[1]
(сообщение с существительным во множественном числе).
Истоки синтаксиса правила Plural-Forms
следует искать в языке С. Как видим, он опирается на тот факт, что булевские выражения в С возвращают 0 или 1.
Имейте в виду, что формы единственного и множественного числа в большой степени зависят от языка. Во многих языках есть несколько форм множественного числа. Например, в польском слово «файл» в единственном числе записывается как «plik». Если количество экземпляров заканчивается на 2, 3 и 4, то во множественном числе пишется «pliki», а во всех остальных случаях – «plików».
Поэтому для польского языка правило Plural-Forms
выглядит так:
Plural-Forms: nplurals=3;
plural=n==1 ? 0 :
n%10>=2 && n%10<=4 && (n%100=20) ? 1 : 2;
Заголовок файла – не пустая формальность. Особенно важны разделы Content-Type
и Plural-Forms
. При пользовании утилитой msginit
они вставляются автоматически, в противном случае необходимо добавить их вручную.
Закончив работу, переводчик посылает файлы обратно разработчику (или вы сами возвращаетесь к роли разработчика).
Файлы myapp.ро
, полученные от переводчиков, помещаются в соответствующие каталоги (внутри каталога myapp/ро
). Например, французскую версию следовало бы поместить в каталог myapp/ро/fr/myapp.ро
, немецкую – в каталог myapp/po/de/myapp.ро
и т.д.
Затем выполните команду rake makemo
. Она преобразует РО-файлы в МО-файлы. Сгенерированные МО-файлы будут помещены в каталог myapp/data/locale/
(в котором есть подкаталоги для каждого языка).
Таким образом, окончательная структура дерева каталогов выглядит так:
myapp/
Rakefile
person.rb
ро/
myapp.pot
de/myapp.po
fr/myapp.po
ja/myapp.po
:
data/
locale/
de/LC_MESSAGES/myapp.mo
fr/LC_MESSAGES/myapp.mo
ja/LC_MESSAGES/myapp.mo
:
Перевод закончен, можно протестировать пример. Но предварительно следует указать, где искать МО-файлы и для какой локали проводится тестирование. Установим переменные окружения GETTEXT_PATH
и LANG
, запустим программу и посмотрим, что она выведет.
export GETTEXT_PATH="data/locale"
export LANG="ja_JP.UTF-8"
ruby person.rb
Программа выводит локализованные сообщения в соответствии со значением переменной lang
.