Pull to refresh
290
0
Игорь Афанасьев @afan

Директор по локализации, Evernote

Send message
Что Phrase, что некоторые другие платформы (не буду их здесь называть), пошли по довольно странному пути абстрагирования от оригинальных форматов файлов в пользу ключей. Этот подход на поверхности выглядит как что-то разумное, однако имеет серьезные недостатки.

И здесь я хочу сказать большое спасибо за подробное описание своего процесса локализации. Вы очень наглядно показали, что при этом происходит — начинается «управление ключами»: интеграция должна обеспечивать их уникальность, подчистку, вы начинаете переживать, когда ключи не переиспользуются между платформами, когда, по вашим словам, возникают «коллизии», вводите, по сути, жесткие зависимости между iOS- и Android-приложением, привязываетесь к стороннему сервису как к неотъемлемой части инфраструктуры. Сам подход к локализации и API таких систем заставляет вас мыслить о процессе локализации не в совсем эффективном русле.

Локализация (с точки зрения процесса) и интеграция (с точки зрения разработки и поддержки) может быть гораздо более простой, если брать существующий «родной» подход и API интернационализации для каждой платформы, использовать нативные форматы файлов ресурсов для каждой платформы и переводить их независимо. Эффективное переиспользование переводов — задача TMS/CAT, а не каждого разработчика в отдельности. И я бы посоветовал не переживать по поводу увеличения стоимости переводов: 100% совпадения практически бесплатны, да и стоимость переводов гораздо ниже стоимости дополнительного времени программистов и менеджеров, задействованных в обслуживании этой системы.
Согласен, опасность такая есть (как и при любом другом подходе к тестированию, в котором есть человеческий фактор). В моей практике такого пока не случалось; все эталонные данные, которые я сохраняю в файлы, всегда структурированы, поэтому при git diff легко обнаружить такие аномалии. Все изменения шаблонов, например, затрагивают одну и ту же строчку, а в одном эталоне картина изменений другая. Конечно, если какое-то изменение затрагивает очень много эталонных данных (сотни файлов), то шанс что-то пропустить сильно увеличивается. С другой стороны, по самому количеству изменившихся эталонов сразу видно, насколько сильно изменение кода затрагивает поведение системы, и что его надо тестировать тщательнее. То же самое и с code review. Представьте коммит, который включает изменение одного шаблона и коммит, в котором включены десятки изменившихся шаблонов. Понятно, что ко второму внимание всех остальных разработчиков будет более пристальным.
Подход можно использовать для реализации разных типов тестов:
  • При тестировании юнитов на небольшом наборе изолированных и специально подготовленных тестов получается, по сути, обычное табличное юнит-тестирование, с тем лишь отличием, что данные находятся в файлах (со всеми плюсами и минусами хранения данных отдельно от тестов; см. data-driven testing и TableDrivenTests), и что эталонные данные не надо писать руками (но обязательно заверять и относиться к ним так, как если бы вы их писали руками).
  • При тестировании больших кусков кода (типа проверки работы API и возвращаемых им стурктур данных) или даже приложения целиком (запускаем приложение с входными параметрами, получаем что-то на выходе, сравниваем выход) получаем интеграционное тестирование. В этом случае входные и выходные данные могут быть более объемными, чем при юнит-тестировании, так что их хранение во внешних файлах более оправданно, как и автогенерация эталонного вывода с последующей его заверкой.
  • При использовании избыточного количества входных данных теста и незаверенных выходных данных получаем pinning tests (спасибо ApeCoder за правильную терминологию). В этом случае мы тестируем не правильность исходной работы приложения, а лишь фиксируем изменения его поведения (что можно делать как, например, отчет в рамках автоматической сборки билдов).

Если порядок в списке не имеет значения, то список нужно отсортировать, чтобы выход был стабильным. Или (если мы говорим конкретно о Go-реализации), положить ошибки в map[string]string — в этом случае при сериализации в JSON ключи будут идти в сортированном порядке автоматически.
В теории — правильно (иметь минимально достаточный объем тестов, полностью покрывающий все сценарии). На практике могут быть случаи, когда проблема может проявиться только на каком-то особом наборе данных. Так что я считаю, что лучше поймать ошибку так (с помощью избыточных тестов), чем не поймать ее вообще. От падения такого теста уже можно плясать и смотреть, какого максимально независимого и понятного теста не хватает.
Здесь все так же, за исключением того, что не нужно руками сериализовать выходные данные. Достаточно их сгенерировать и заверить.

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

Работа со слепками данных позволяет автодокументировать работу как отдельных компонентов, так и всей системы, и выявлять такие ошибки, которые на бумажке не обнаружишь. Пример: функция должна выдавать некую структуру с полями A, B и C. На бумажке все хорошо, в тесте все хорошо — мы явно проверяем наличие этих трех полей и их значения. И все совпадает с ТЗ. А посмотрев на слепок данных вдруг оказывается, что с какого-то момента функция начинает еще возвращать и поле D. В ТЗ это не прописано, но такая ошибка может быть совсем не безобидной (приводить к утечке конфиденциальных данных, например).
Второй раз бизнес-логику в тестах все равно никто не пишет. Все выливается в итоге в подготовку fixtures, запуск куска основного бизнес-кода и в сравнение каких-то параметров выходных данных (при котором легко что-то забыть сравнить). Здесь же сраниваются снэпшоты данных целиком. Да, они генерятся тестами, но после этого их надо проверять, иначе нет смысла все это затевать. Но сам код теста (сравнение снэпшотов данных) как раз по определению отличается от кода бизнес-логики, и чтение готового снэпшота данных, проверенного и отвечающего условиям ТЗ, и дает этот второй независимый путь.

Да, approval tests — это тот же принцип.

Да, очень-очень близко. Спасибо за ссылку!

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


Можно поспорить, что считать хорошим или плохим тестом; с традиционными юнит-тестами я сталкивался как раз с тем, что в юнит-тесте воспроизводился кусок бизнес-логики, создающий объекты и подготавливающий данные, просто потому что это было легко. В итоге тест был плохой, потому что ошибка в бизнес-коде воспроизводилась и в тесте, и тест проходил на ура. Что мне ноавится в подходе с эталонными данными, это то, что код теста там гарантированно другой, и результат всегда нагляден (можно посмотреть, обсудить и прокомментировать в системе code review), и шансов скопипастить багу из основногл кода в тестовый сильно меньше из-за другой природы тестов.

Согласен, ошибку в эталонном файле можно ненароком пропустить. И к тому же данный способ провоцирует на создание большего количества тестов, что приводит к большему количеству эталонных данных (которые все надо тщательно проверять). With great power comes great responsibility...

Это, разумеется, data driven tests, но только которые сами генерят выходные данные — в этом основная суть подхода.
По-моему все это очень даже в духе TDD. Тесты никуда не уходят, их только создавать и поддерживать становится легче. Схема такая: пишем сначала тест, формирующий API бизнес-логики, и подготавливаем для него входные данные (тест не проходит); пишем основной код приложения; проверяем код через запуск теста в режиме инициализации и генерацию эталона; rinse & repeat. Можно, конечно, и эталоны генерить руками, только зачем? Главное понимать, какие данные должны быть на выходе (сформулировать для себя эталонный вывод теста), а формально файлик пусть компьютер создает. Ну и коммитить тест и результаты теста вместе с основным бизнес-кодом.
С Transifex мы с ними общаемся, может быть даже попробуем сделать поддержку Transifex в Serge.

Формально, поддержки непрерывной локализации в самом Transifex нет. Есть их утилита командной строки, которую можно использовать в своих скриптах. Но добавление новых файлов, удаление старых, начальная выгрузка, конфигурирование на стороне веб-интерфейса — все это надо делать руками. И потом, когда это сделано, можно написать свои кастомные скрипты, которые будут делать VCS push/pull и tx push/pull. И опять же потенциально иметь проблемы с мержем на уровне VCS или же писать скрипты для обхода этого. А Serge позволяет однажды настроить процесс и забыть про ручную работу.

Pootle у вас является основным внутренним CAT-инструментом? И именно это и послужило одним из драйверов создания Serge? У меня на работе несколько иная схема, где в ядре находится memoQ.

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

Я, по правде говоря, и сейчас не знаю других продуктов для непрерывной локализации. Понятно, что в последнее время появилось много CAT-решений, но когда дело касается интеграции процесса внтури компании с этим CAT-решением, обычно требуется интеграция через API (или через клиент командной строки), или же интеграция происходит по принципу «нажми на кнопку, чтобы загрузить новые строки, нажми другую кнопку, чтобы выгрузить переводы». Что у memoQ в этом плане?

Описанная вами цепочка работает только на локализации интерфейсов? Пробовали ли вы применить Serge для локализации документации?

Документация у нас вся хранится в виде статей в Zendesk. Есть еще маркетинговые материалы (блог-посты, описания приложений для app store, release notes), которые хранятся в Google Drive. Как для Zendesk, так и для Google Drive мы применяем похожую схему: есть скрипты синхронизации, который забирают контент из внешнего сервиса, генерят локально XML-файлы по одному на документ и выгружают это в систему контроля версий. Дальше все это переводится с помощью Serge+Pootle, а затем эти же скрипты синхронизации выгружают локализованный контент обратно в Zendesk и Google Drive.

Если у вас документация хранится уже в системе контроля версий в каком-то пригодном к парсингу формате, то ее можно обрабатывать с помощью Segre безо всяких дополнительных ухищрений. Если интересно, пишите в личку, пообщаемся более предметно.
Хорошие вопросы.

Образно говоря, translate-toolkit по сравнению с Serge — это Arduino по сравнению с iPhone. Мы активно помогаем разрабатывать Pootle, но рассматриваем его лишь как интерфейс для перевода, не более. Serge+Pootle = полноценная опенсорс-платформа для непрерывного перевода. translate-toolkit мы помогать развивать не намерены. Внутри самого Pootle есть также тенденция уйти от зависимости с translate-toolkit.

Serge не является конвертером форматов в прямом смысле этого слова. И это сильно больше, чем набор парсеров — рекомендую почитать документацию к управляющим плагинам, например. Снапшоты базы и .po-файлов мы делаем — ничего сложного тут нет. База не такая большая — можно делать бэкап хоть после каждого цикла. Elasticsearch — это TM внутри самого Pootle. Serge позволяет использует свою базу как TM, переиспользует известные переводы (сначала пытается находить 100% матчи внутри файла, потом внутри проекта, затем внутри всей базы).

Помогать развивать функционал Pootle мы, разумеется, продолжим. Есть много идей для улучшения.
Не все, что удобно в разработке, удобно и быстро для конечных пользователей продукта. Динамическая локализация удобна переводчикам (коих у нас около 30), но имеет свои минусы для инженеров (коих у нас сотня) и конечных пользователей (коих миллионы). Одно дело — перенос своих вычислительных мощностей на AWS. Это прозрачно для пользователей, и скорее всего даже призвано ускорить работу. Другое дело — локализация в рантайме (усложнение кода и процесса).
Поддержка форматов даты и чисел обычно есть в операционной системе — ничего особенного в рамках локализации тут делать обычно не надо. Разработчикам нужно просто следовать правилам, заданным для их платформы.
Сервисы такие есть. Qordoba, например. Но, во-первых, это платно (и цены обычно сильно зависят от размера компании). Во-вторых, ни одна уважающая себя компания не будет ставить свой продукт (рантайм) в зависимое положение от стороннего решения. Это усложняет код, делает его более медленным, это делает локализационное решение нестандартным (чего разработчики не любят), это усложняет тестирование. Наконец, зачем что-то делать в рантайме миллионы раз, когда можно сделать один раз статически?
На уровне Serge есть только возможность протаскивать коммаентарии разработчиков из файлов ресурсов на сервер перевода. Уже на самом сервере переводчики видят эти комментарии, а также скриншоты, которые добавляют менеджеры по локализации. Интерфейс наши инженеры разрабатывают таким образом, чтобы он корректно работал с более длинными строками, а тестирование локализованных сборок позволяет нам выявлять косяки и переводить что-то покороче, когда это возможно, или файлить баги на разработчиков, чтобы они где-то переработали интерфейс.

Information

Rating
Does not participate
Location
California, США
Date of birth
Registered
Activity