Знакомство с транслитерацией

Обычной работой лингвистов является транслитерация, то есть преобразование одной орфографии (набора правил написания определенного языка) в другую.

Например, лингвисты, как правило, используют упрощенную или практическую орфографию в своих заметках и базах данных, а не Международный фонетический алфавит (IPA), а затем преобразуют свои данные в IPA или общепринятую американскую нотацию. для публикации. Более того, лингвисты часто работают с сообществами, у которых есть собственная предпочтительная орфография, которая может совпадать или не совпадать с практической орфографией лингвиста. Создание материалов для сообщества требует транслитерации с практической орфографии на орфографию сообщества. И лингвисты обычно работают с данными из архивных источников и предшествующих исследователей, каждый из которых мог использовать различную орфографию для написания языка.

Моя собственная база данных Chitimacha (изолят, Луизиана; ISO 639–3: ctm, Glottolog: chit1248) использует не менее 8 орфографий, многие из которых исходят от предыдущих исследователей:

Современные орфографии

  1. Современная общественная орфография
  2. Международный фонетический алфавит
  3. Американистская нотация

Исторические орфографии

  1. Фонетическая запись Морриса Сводеха (1930–1934)
  2. Фонематическая запись Морриса Сводеша (1930–1952)
  3. Обозначения Джона Р. Суэнтона (1907–1920)
  4. Обозначения Альберта С. Гатше (1881–1882 гг.)
  5. Обозначения Мартина Дюральде (1802 г.)

В прошлом это означало, что я постоянно вручную транслитерировал то одну, то другую историческую орфографию в ту или другую современную орфографию. Часто одну и ту же фразу-пример необходимо транслитерировать несколько раз, в зависимости от конкретного издания (например, американистское обозначение для International Journal of American Linguistics, но IPA для более общих журналов). Очевидно, что это было чревато ошибками и отнимало много времени.

Базовая транслитерация

По сути, транслитерация — это простая процедура, в ходе которой выполняется ряд замен в строке. Например, чтобы транслитерировать написанное слово cat в IPA, нам нужно сделать две замены: <c> → <k> и <a> → <æ>.

Обратите внимание, что этот метод транслитерации является однонаправленным. Если бы мы хотели транслитерировать обратно с IPA на английский, нам нужно было бы сделать другой набор замен: <k> → <c> и <æ> → <a>. Иногда можно выполнить двунаправленную или многонаправленную транслитерацию, когда каждая графема (письменный символ) в орфографии соответствует одной и только одной графеме в другой орфографии. Однако двунаправленная транслитерация часто невозможна из-за сложности различных систем письма. Некоторые различия могут быть потеряны в процессе транслитерации, что делает невозможным обратный процесс без потери информации. Таким образом, алгоритм DLx transliterate() является однонаправленным.

Имея это в виду, давайте настроим нашу функцию так, чтобы она принимала два аргумента: string (строка для транслитерации) и substitutions (список замен, которые нужно сделать в строке). Мы можем назвать набор замен схемой транслитерации, то есть набором правил замены для транслитерации из одной орфографии в другую. Аргумент substitutions должен быть объектом, ключами которого являются заменяемые графемы, а значениями — замещаемые графемы. Например, схема транслитерации для нашего примера cat выше будет выглядеть так:

const substitutions = {
  a: 'æ',
  c: 'k',
};

Вот как мы будем использовать наш метод transliterate(), когда он будет готов:

const ipa = transliterate('cat', substitutions);
console.log(ipa); // --> "kæt"

Внутри метода transliterate() нам нужно будет выполнить каждую замену в аргументе substitutions с помощью регулярных выражений. Вот как мы можем это сделать:

Выполнение этой функции в нашем примере cat дает нам правильный результат: /kæt/ — мы успешно транслитерировали строку! Теперь давайте рассмотрим некоторые крайние случаи, которые могут нарушить работу нашей функции, и способы их решения.

Пограничный случай 1: подстроки

Допустим, у нас есть следующая схема транслитерации:

{
  s:  'x',
  ts: 'c',
}

Если мы запустим нашу текущую версию функции transliterate() для строки "tsaste" («касание» в читимача), мы получим "txaxte" в качестве вывода — неверный результат! В соответствии с нашей схемой транслитерации мы хотим, чтобы на выходе было "caxte".

Что случилось? Проблема в том, что ввод "s" является подстрокой ввода "ts". Когда наша функция делает первую замену "s" → "x", строка "ts" заменяется на "tx". Когда наша функция пытается сделать вторую замену ("ts" → "c"), она терпит неудачу, потому что в строке не может быть найдена последовательность "ts". Каждый экземпляр "ts" уже был изменен на "tx".

Чтобы решить эту проблему, нам просто нужно упорядочить замены от самой длинной строки до самой короткой строки, прежде чем делать замены. Вы можете сами убедиться в этом вручную, запустив transliterate() по приведенной ниже схеме транслитерации. Правила замены те же, что и раньше, но их порядок другой. На этот раз мы получаем желаемый результат.

{
  ts: 'c',
  s:  'x',
}

Чтобы программно отсортировать правила подстановки, мы просто запускаем метод .sort() в нашем списке подстановок перед вызовом метода .forEach(), например так (обратите внимание на добавленный метод .sort()):

Пограничный случай 2: кормление

Для второго пограничного случая рассмотрим, что произойдет, если мы запустим нашу функцию transliterate(), используя следующую схему транслитерации для строки "chaca".

{
  ch: 'c',
  c:  'x',
}

Используя нашу текущую версию метода transliterate(), мы получаем неверный результат: "xaxa" вместо ожидаемого "caxa".

Проблема здесь в том, что известно как проблема подачи в лингвистике (особенно в области фонологии), когда результат одного правила/подстановки также является входом для другого правила/подстановки. В приведенном выше примере наша функция сначала изменяет все экземпляры "ch" на "c", а затем изменяет все экземпляры "c" на "x", не оставляя ни одного экземпляра "ch" или "c" в конечном выводе.

Чтобы исправить это, мы сначала проверяем список подстановок на наличие проблем с кормлением, а затем разрываем цепочку кормления, временно заменяя вывод неправильного правила каким-либо другим случайным символом. Затем, после того, как замены сделаны с использованием случайного символа, мы заменяем правильный символ обратно. Для случайной временной замены символа мы будем использовать символ Unicode из блока геометрических фигур, так как маловероятно, что кто-то будет использовать символы из этого кодового блока в орфографии (к тому же это было бы крайне плохой практикой).

Вот окончательный код функции transliterate(), которая сортирует замены и правильно обрабатывает проблемы с подачей. Обратите внимание, что теперь у нас есть объект temps для хранения наших временных замен проблем с кормлением, метод getRandomCodePoint() для выбора случайной точки Unicode в блоке геометрических фигур и несколько новых шагов в коде, где мы делаем временную замену, а затем отменяем ее. позже.

И это все! Теперь функция transliterate() должна давать правильный результат для описанной выше проблемы с кормлением.

Найдите какие-либо другие пограничные случаи, которые этот алгоритм не обрабатывает? Пожалуйста, дайте мне знать в комментариях!

Вывод

Транслитерация оказывается несколько сложнее, чем кажется на первый взгляд. Однако иметь дело с крайними случаями, такими как подстроки и подача, на самом деле довольно просто. Результатом стала компактная и универсальная функция, способная транслитерировать любую орфографию в другую.