Проработав год в Японской компании системным инженером, я выделил несколько моментов, которые обязательно должен знать программист, работающий с японским текстом.

В Японском языке 3 алфавита, что делает работу с текстом не такой простой, как в европейских языках. Начнем с самого простого и перейдем к более сложному.

Как и в России, в Японии остались сайты с локальной кодировкой, переводим текст в UTF-8:

$output = iconv('Shift-JIS', 'UTF-8//IGNORE', $input);

Есть такая особенность у японцев: текст может быть записан двумя способами — так называемая «полная длина» (Zen-Kaku) и «половина длины» (Han-Kaku). Японский текст — это Zen-Kaku, европейский текст — это Han-Kaku, но в принципе любой европейский текст может быть записан как Zen-Kaku. Пример: SALE. Но особенно японцы любят зен-каку цифры: 123.

Чтобы перевести SALE в SALE:

public function toHanKaku($text)
{
    return mb_convert_kana($text, 'a');
}

Зачем же японцам 3 алфавита? В первом кандзи (??) — записываются иероглифы. Второй — это хирагана (????), это, можно сказать, главный алфавит, потому что любой кандзи может быть записан хираганой. И третий — это катакана (????) и она — это то же самое, что и хирагана, за исключением того, что ею записываются иностранные слова.

Для перевода хираганы в катакану (??? в ???) (одно и то же слово в разных алфавитах) воспользуемся той же функцией, только с другим ключем:

public function toKatakana($text)
{
    return mb_convert_kana($text, 'C');
}

Та же самая проблема и с регулярными выражениями. Есть дата в формате: 2016.20.20. Чтобы она распознавалась, обязательное условие модификатор «u» — Unicode.

preg_match('/[\d]{4}.[\d]{2}.[\d]{2}/u', $text, $match);

Как и все, японцы не заморачиваются с адресами страниц и пишут прям в адрес японскими иероглифами, например, так:

<a href="http://ja.wikipedia.org/wiki/???">???</a> 

Адрес верный, но чтобы его понимал, например, Curl, он должен быть закодирован:

public function japaneseUrlEncode($text) 
{
        return preg_replace_callback(
        '/[^\x21-\x7e]+/', function($matches) {
            return urlencode($matches[0]);
        }, $text
    );
}

Система ввода японского текста тоже особенная, она позволяет прямо с клавиатуры без дополнительных инструментов добавлять разные украшения: dd??????. Всеми этими штуками буквально переполнены блоги, форумы, твиттеры и тому подобные места.

Проблема же заключается в том, что при попытке вставить некоторые эмодзи в обычную таблицу utf8_general_ci, мы получим обрезанный текст и ошибку:

Warning: #1366 Incorrect string value: '\xF0\x9F\x98\x8ASI...' for column 'content' at row 1

А дело тут в том что utf8_general_ci — это конечно очень богатый набор символов, но он ограничен 3 байтами на символ и не содержит 4-байтовые эмодзи. Кодировка которая подойдет — это utf8mb4.

Пример текстовой красоты по-японски:
??????????SIENA???????????
?????????new?????????d


Но не всем подойдет менять кодировку, есть другое решение — удаляем 4-байтовые эмодзи:

public function clear4byte($string)
{
    return preg_replace('%(?:
        \xF0[\x90-\xBF][\x80-\xBF]{2} |
        [\xF1-\xF3][\x80-\xBF]{3} |
        \xF4[\x80-\x8F][\x80-\xBF]{2}
    )%xs', '', $string);    
}

Надеюсь, данный материал будет полезен тем, кто работает с японским текстом, а также всем остальным для общего развития.

P.S.: Первая публикация не удалась, комментарий модератора:
Ощущение, что статья обрывается где-то посередине. Дополните материал и опубликуйте еще раз.

То, что называется «дежавю» — с хабром произошел как раз случай, описанный в последнем примере, а именно: я добавил для наглядности символы в четырехбайтовой кодировке, что вызвало ошибку, и текст был сохранен в базу данных в обрезанном виде.
Поделиться с друзьями
-->

Комментарии (27)


  1. psrafo
    29.06.2016 12:32
    -1

    Ох, интересно то как, не, серьезно.


    1. mnakamura
      29.06.2016 13:10
      +1

      Только ошибок и неточностей вагон и маленькая тележка:

      Как и в России, в Японии остались сайты с однобайтовой кодировкой, переводим текст в UTF-8:


      Какая же она однобайтовая? Она многобайтовая, как и UTF-8, почти все, кроме ASCII как раз в 2 байта кодируется.
      Есть такая особенность у японцев: текст может быть записан двумя способами — так называемая «полная длина» (Zen-Kaku) и «половина длины» (Han-Kaku).


      Стоит добавить, что в целом в шрифтах сильно поощряется моноширинность. Hankaku — это по сути те же codepoints, что в ASCII — но за счет форсируемого моноширинного шрифта они становятся ровно в половину знакоместа.
      Но особенно японцы любят зен-каку цифры


      Ларчик просто открывается: во всеми любимой операционной системе они вводятся по умолчанию при включенном Japanese IME. Как и full-width скобочки, точки, двоеточия, вопросительные/восклицательные и всякие такие знаки препинания.
      Адрес верный, но чтобы его понимал, например, Curl, он должен быть закодирован:


      Адрес надо URL-encode'ить в любом случае, японский ли это или нет. И тем более крайне странно использовать такую выборочную функцию.
      без дополнительных инструментов добавлять разные украшения: dd??????.
      но он ограничен 3 байтами на символ и не содержит 4-байтовые эмодзи


      Из того, что вы привели, ни один символ не является расширенным 4-байтовым emoji. Все это прекрасно кодируется в 3-байтовый UTF-8. Я понимаю, что у вас сохранить не получилось из-за странностей хабрового движка, но зачем народ в заблужение-то вводить?


      1. Settler1
        29.06.2016 13:28

        Какая же она однобайтовая?

        Она действительно 1-2 байтовая. Спасибо что поправили.

        И тем более крайне странно использовать такую выборочную функцию.

        Если кодировать весь адрес, то будут закодированы также :// и другие символы, в результате, получим не работающую ссылку.
        Более того даже в спецификации этой функции написано что кодироваться должна именно часть, как правило передаваемые параметры: http://php.net/manual/ru/function.urlencode.php Поэтому нужно именно выборочно.

        Из того, что вы привели, ни один символ не является расширенным 4-байтовым emoji. Все это прекрасно кодируется в 3-байтовый UTF-8.

        Нельзя. Конечно нельзя. Если адрес символа зарезервирован на 4 байтах, перевести его в 3 байта невозможно. Убрав 1 байт вы получите просто другой символ. Хабр 4 байта не сохраняет.


        1. mnakamura
          29.06.2016 13:50

          Поэтому нужно именно выборочно.

          Но не так же! Собственно, даже в приведенном вами PHP manual написано: разумеется, нужно понимать семантику кодируемого и, как правило, разделять URL на составные части и кодировать по частям. Причем разные части кодируются по разному: домен — в punycode, path — percent-encoding с одним набором «запрещенных» ASCII символов, query — другим, fragment — остается в unicode (если речь не о CURL, скажем, а о каких-то браузерах). То, что предлагаете вы — просто неверно (а местами и опасно). Попробуйте закодировать, скажем, какой-нибудь http://???.jp/ вашей функцией — я посмотрю, что получится.

          Если адрес символа зарезервирован на 4 байтах, перевести его в 3 байта невозможно.

          Это все отлично, только для демонстрации этого вы используете 3-байтовые символы. Я понимаю, что вставить напрямую не удалось — ну так вставьте картинками, или добавьте ссылку на какой-нибудь сервис, корректнее работающий с unicode, или еще что-нибудь. Пока получается, что примеры в статье несколько противоречат содержимому.


          1. Settler1
            29.06.2016 14:28

            Попробуйте закодировать, скажем, какой-нибудь http://???.jp/ вашей функцией

            Ею кодируется строка query, а не url. Это вопрос в том как вы её будите применять.


      1. symbix
        30.06.2016 00:32

        В случае с mysql и Shift-JIS, кстати, если не выставлена корректно _клиентская_ кодировка и, соответственно, libmysql производит экранирование в однобайтовом режиме — можно протащить SQL injection, основанный на том, что в Shift-JIS есть вполне валидные символы, где последний байт совпадает с бэкслешом (0x5c).


        1. mnakamura
          30.06.2016 11:44

          Это, кстати, обычно не работает, т.к. если libmysql производит экранирование в некоторой кодировке, то почти всегда она же и будет этот запрос интерпретировать, причем в той же кодировке. Может сработать в некоторых экзотических случаях, когда между экранированием и выполнением запроса есть дополнительные стадии, второй раз заменящие кодировку.


          1. symbix
            30.06.2016 15:20

            Так в том и дело, что запрос будет интерпретировать сервер. И сработает именно тогда, когда корректно выставили кодировку Shift-JIS для сервере (например, выполнив запрос set names), но не выставили кодировку для клиентской библиотеки, и она считает, что работает с дефолтной latin1.

            С UTF-8 никаких проблем при такой некорректной конфигурации заметно не будет, так что подобные уязвимости, проявляющиеся только с «экзотическими» мультибайтными кодировками могут присутствовать в массе продуктов.


  1. gaki
    29.06.2016 12:33

    Боюсь даже представить, насколько данная тема актуальна для основной аудитории :)


    1. Telescopium
      01.07.2016 10:16

      На апворке, к примеру, встречаются задания от американцев, работающих с сайтами с ЮВА


      1. gaki
        01.07.2016 11:48

        ?????!(???????)?


        1. Telescopium
          01.07.2016 11:54

          удача в этом деле точно не помешает :)


          1. gaki
            01.07.2016 11:57

            Таки гугль транслейт? :)


            1. Telescopium
              01.07.2016 12:09

              Hai


  1. overmes
    29.06.2016 12:33

    А как там в Японии? Программисты нужны?


    1. Settler1
      29.06.2016 12:45
      +1

      Нужны


      1. Mirn
        29.06.2016 12:55

        а программисты железа, ПЛИС и микроконтроллерщики (rtos and baremetal) с знанием японского языка?


        1. Settler1
          29.06.2016 13:02

          И эти тоже, у них своя электронная промышленность тоже развита и такие, наверно, даже ещё более нужны.


  1. alutskevich
    29.06.2016 12:34

    Мне для общего развития пойдёт) Спасибо автору!


  1. mnakamura
    29.06.2016 13:26
    +1

    Вообще в целом вы затрагиваете очень непростую тему и к ней нельзя относиться так легко — мол, два регэкспа и вперед. Тема связана с нормализацией и сравнением строк в языке, где правила этого самого сравнения сильно отличаются от европейских и ожидания людей тоже отличаются.


    Например, огромная туча людей будет справедливо считать, что ?, ??? и ??? (а зачастую и "midori") — это одно и то же. И будут в целом правы. Это примерно как на русском языке считать, что "зелёный", "зеленый" и "ЗЕЛЕНЫЙ" — это одно и то же.


    Есть куда более сложные примеры — когда нужно считать фонетические эквиваленты. Например, то же самое ????? люди пишут иногда как ????? ("как слышу — так и пишу") или даже ????, например, пропустив двойную "n".


    А иногда, например, когда речь идет об обработке баз данных с именами-фамилиями, так наоборот нельзя делать ни в коем случае, потому что ?? ("защитница утреннего солнца") и ?? ("ясная голова") — две совершенно разных девушки, хотя и то, и другое произносится как "Akiho". И эти случаи надо отдельно выделять и оформлять подобным образом.


    Продолжать можно еще очень долго.


    1. Dreyk
      29.06.2016 13:51
      +2

      Мне чисто ради интереса, «как слышу — так и пишу» — это как «малако» вместо «молоко» или это тоже правильное написание?

      Не смог удержаться
      Русский, французский и китайский лингвисты решили написать имена друг друга, каждый на своём языке.

      — Моя фамилия Ге, — сказал француз китайцу.
      — В китайском языке два иероглифа Ге, но, к сожалению, ни один из них не подходит для фамилии.
      — Почему?
      — Потому что один имеет значение «колесо», а другой передает звук, с которым лопается мочевой пузырь осла.
      — А что плохого в колесе?
      — Мужское имя не может быть круглым. Для твоего имени мы возьмем иероглиф Шэ, означающий «клавиатура», «корнеплод», «страница», а также прилагательное «бесснежный» и дополним его иероглифом Нгу, означающим мужской род. В конце я пишу иероглиф Мо — «девственный».
      — Но это, мягко говоря, не совсем…
      — Никто не будет считать тебя девственником, просто без иероглифа Мо иероглифы Ше-Нгу означают «сбривающий мамины усы».

      — Хорошо, теперь я напишу твое имя.
      — Моя фамилия Го.
      — Отлично, я начну твою фамилию с буквы G.
      — Что означает буква G?
      — У нас, европейцев, сами по себе буквы ничего не значат, но чтобы проявить к тебе уважение, я поставлю перед G букву H — во французском она все равно не читается.
      — Отлично! Дальше O?
      — Нет, чтобы показать, что G — произносится как Г, а не как Х, надо после G поставить букву U, а также H — чтобы показать, что U не читается сама по себе, а только показывает, как правильно читать G, и буквы EY, показывающие, что слово не длинное и скоро закончится.
      — Hguhey… дальше O?
      — Нет, О во французском произносится как А или Ё, в зависимости от стоящих по соседству букв, ударения и времени года. Твое чистое О записывается как AUGHT, но слово не может кончаться на T, поэтому я добавлю нечитаемое окончание NGER. Вуаля!

      Русский лингвист поставил бокал на стол, взял листочек и написал «Го» и «Ге».
      — И всё?
      — Да.

      Француз с китайцем почесали в затылке.
      — Хорошо, а какая у тебя фамилия?
      — Щекочихин-Крестовоздвиженский.
      — А давайте просто выпьем? — первым нашёлся китаец.

      Русский кивнул и француз с облегчением поднял тост за шипящие дифтонги.


      1. mnakamura
        29.06.2016 14:06
        +1

        Да, примерно так. ????? (konnichiwa) — традиционное японское нейтральное приветствие (типа «добрый день»). Проблема в том, что на записи последний слог формально «ha», а не «wa». Если взять само «konnichi» — это просто «этот день». Частица ? после члена предложения обычно показывает что это слово — подлежащее. И когда ? является именно этой самой частицей, ее надо произносить как «wa», а не «ha». Вообще, в японском очень простые правила чтения (примерно как в русском), исключений по пальцам пересчитать, но вот такое есть.

        В обычных предложениях никто массово опечаток не делает (все пишут «ha», произносят «wa» и никакого когнитивного диссонанса не испытывают). С приветствием же проблема в том, что оно как бы полноценным предложением не является и устоялось в такой форме чисто по историческим причинам. Все на автомате произносят и никто даже не задумывается о том, что это значит дословно. И точно так же на автомате иногда пишут. Я в свое время даже по входящей почте статистику считал — на 100 писем с приветствием эдак 5-7 «неверных» записей точно будет. Причем письма вполне от чистокровных японцев, не от каких-нибудь понаехавших студентов.


      1. mnakamura
        29.06.2016 14:19
        +1

        Кстати, на японском «Щекочихин» отлично записывается как ?????? — «she-ko-chi-hi-n».

        Гораздо хуже со всякими там «gasoline» (который выглядят страшно на записи — ????), всякими там фамилиями типа «Волобуев» (которая становится «borobuebu») или с сосредоточением согласных типа «Мкртчян» (с другом могу представить).


      1. Settler1
        29.06.2016 14:31

        Ну скорее как «Здравствуйте» и «Здрасте», то есть не правильно, но в просторечии — применимо.


  1. Telescopium
    01.07.2016 09:51

    4-байтовый смайл \xF0\x9F\x98\x8A' можно заменить на 3-байтовый '\xE2\x98\xBA'
    но делать это для всех эмодзи ещё тот праздник :)


    1. mnakamura
      01.07.2016 12:35

      Они от этого обычно становится не такими цветными. Ну и, да, там их сейчас несколько тысяч, включая всякую платформо-эксклюзивную фигню типа «два мужчины держатся за руки и перед ними мальчик и девочка».


      1. Telescopium
        01.07.2016 12:41

        Абсолютно согласен.