Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Автоматизация локализации iOS-приложений

Автоматизация локализации iOS-приложений

Deck для моего выступления на MBLT'12 в рамках Yandex Mobile Camp. Текст изначального анонса ниже:

Я расскажу про ужасы локализации и как с ними бороться на пошаговом примере: от «Эврика, нам нужно перевести проект на язык Х!» до «Как не прострелить себе ногу, когда у вас есть Xcode, разработчики, переводчики и дедлайн».

Мы пройдемся по всем базовым инструментам локализации (genstrings, ibtool) и поймем, как их использовать с наименьшими телодвижениями. Отдельно поговорим про то, как мы подружили весь этот «зоопарк» в Яндексе и чем готовы поделиться.

Andrey Subbotin

May 08, 2012
Tweet

More Decks by Andrey Subbotin

Other Decks in Technology

Transcript

  1. 52,4% не покупают продукт на чужом языке. 60% — для

    Франции, Японии и России. 89,3% — если английский знают плохо. 5
  2. 19

  3. en.lproj/Localizable.strings /* A text string to be output to the

    logs. */ "Some sample text" = "Some sample text"; /* A text string to be output to the logs. */ "Some sample text" = "Некий примерный текст"; 30 ru.lproj/Localizable.strings
  4. /* Class = "IBUIButton"; normalTitle = "Welcome!"; ObjectID = "8";

    */ "8.normalTitle" = "Welcome!"; 33 en.lproj/ViewController.strings
  5. генерирует .strings из кода и XIB’ов загружает .strings в Tanker

    забирает из Tanker’а свежие переводы обновляет XIB файлы аккуратно все коммитит в git PROFIT!!
  6. Babelyoda::Specification.new do |s| s.name = 'YandexMaps' s.development_language = :en s.localization_languages

    = [:ru, :uk, :tr] s.engine = Babelyoda::Tanker.new do |t| t.token = ENV['TANKER_TOKEN'] t.project_id = 'myak_iphone' t.endpoint = ENV['TANKER_HOST'] end s.scm = Babelyoda::Git.new s.source_files = FileList['{Classes,Shared}/**/*.{m,mm,h}'] s.resources_folder = 'Resources' s.xib_files = FileList['Resources/**/en.lproj/*.xib'] s.strings_files = FileList['Resources/**/en.lproj/*.strings'] end 62
  7. Babelyoda::Specification.new do |s| s.name = 'YandexMaps' s.development_language = :en s.localization_languages

    = [:ru, :uk, :tr] s.engine = Babelyoda::Tanker.new do |t| t.token = ENV['TANKER_TOKEN'] t.project_id = 'myak_iphone' t.endpoint = ENV['TANKER_HOST'] end s.scm = Babelyoda::Git.new s.source_files = FileList['{Classes,Shared}/**/*.{m,mm,h}'] s.resources_folder = 'Resources' s.xib_files = FileList['Resources/**/en.lproj/*.xib'] s.strings_files = FileList['Resources/**/en.lproj/*.strings'] end 63
  8. Babelyoda::Specification.new do |s| s.name = 'YandexMaps' s.development_language = :en s.localization_languages

    = [:ru, :uk, :tr] s.engine = Babelyoda::Tanker.new do |t| t.token = ENV['TANKER_TOKEN'] t.project_id = 'myak_iphone' t.endpoint = ENV['TANKER_HOST'] end s.scm = Babelyoda::Git.new s.source_files = FileList['{Classes,Shared}/**/*.{m,mm,h}'] s.resources_folder = 'Resources' s.xib_files = FileList['Resources/**/en.lproj/*.xib'] s.strings_files = FileList['Resources/**/en.lproj/*.strings'] end 64
  9. Babelyoda::Specification.new do |s| s.name = 'YandexMaps' s.development_language = :en s.localization_languages

    = [:ru, :uk, :tr] s.engine = Babelyoda::Tanker.new do |t| t.token = ENV['TANKER_TOKEN'] t.project_id = 'myak_iphone' t.endpoint = ENV['TANKER_HOST'] end s.scm = Babelyoda::Git.new s.source_files = FileList['{Classes,Shared}/**/*.{m,mm,h}'] s.resources_folder = 'Resources' s.xib_files = FileList['Resources/**/en.lproj/*.xib'] s.strings_files = FileList['Resources/**/en.lproj/*.strings'] end 65
  10. Babelyoda::Specification.new do |s| s.name = 'YandexMaps' s.development_language = :en s.localization_languages

    = [:ru, :uk, :tr] s.engine = Babelyoda::Tanker.new do |t| t.token = ENV['TANKER_TOKEN'] t.project_id = 'myak_iphone' t.endpoint = ENV['TANKER_HOST'] end s.scm = Babelyoda::Git.new s.source_files = FileList['{Classes,Shared}/**/*.{m,mm,h}'] s.resources_folder = 'Resources' s.xib_files = FileList['Resources/**/en.lproj/*.xib'] s.strings_files = FileList['Resources/**/en.lproj/*.strings'] end 66
  11. $ rake -T rake babelyoda rake babelyoda:create_keysets rake babelyoda:drop_empty_strings rake

    babelyoda:drop_orphan_keys rake babelyoda:drop_orphan_keysets rake babelyoda:extract rake babelyoda:extract_strings rake babelyoda:extract_xib_strings rake babelyoda:fetch_strings rake babelyoda:initBabelfile rake babelyoda:localize_xibs rake babelyoda:pull rake babelyoda:push rake babelyoda:remote:drop_keysets rake babelyoda:remote:list rake babelyoda:verify 68
  12. #!/bin/bash function verify { if [ $CONFIGURATION == 'AppStore' ]

    ; then rvm rvmrc trust . && rvm rvmrc load . && bundle \ && bundle exec rake babelyoda:verify return $? fi return 0 } git submodule update --init --recursive && verify 69 yxbuildkit-prebuild.sh
  13. NSLog( NSLocalizedString(@"I scanned %g %@.", @”Text to show the number

    of directories scanned”), dirScanCount, dirScanCount == 1 ? NSLocalizedString(@"directory", @”Single directory”) : NSLocalizedString(@"directories", @”Plural directories”) ); 77
  14. NSLog( dirScanCount == 1 ? NSLocalizedString("I scanned %g directory.", @”Blah”)

    : NSLocalizedString("I scanned %g directories.", @”Blah”), dirScanCount ); 83
  15. NSString *forms[4] = {0}; forms[0] = NSLocalizedString(@"%d change", @"Blah"); forms[1]

    = NSLocalizedString(@"%d changes", @"Blah"); forms[2] = NSLocalizedString(@"%d changes", @"Blah"); forms[3] = NSLocalizedString(@"%d changes", @"Blah"); int form = YXPluralFormForN(self.transfersCount); NSString *pluralTransfers = forms[i]; 86
  16. int YXPluralFormForRU(NSInteger n) { // One - 1, 21, 31,

    ... // Some - 2-4, 22-24, 32-34 ... // Many - 5-20, 25-30, ... NSInteger n10 = n % 10; if ((n10 == 1) && ((n == 1) || (n > 20))) { return 0; } else if ((n10 > 1) && (n10 < 5) && ((n > 20) || (n < 10))) { return 1; } else { return 2; } } 87
  17. NSString *forms[4] = {0}; forms[0] = NSLocalizedString(@"NumberChanges0", @"Blah"); forms[1] =

    NSLocalizedString(@"NumberChanges1", @"Blah"); forms[2] = NSLocalizedString(@"NumberChanges2", @"Blah"); forms[3] = NSLocalizedString(@"NumberChanges3", @"Blah"); int form = YXPluralFormForN(self.transfersCount); NSString *pluralTransfers = forms[i]; 90
  18. /* The number of changes shown in the route description

    */ "%[one]d changes" = "%d changes"; "%[some]d changes" = "%d changes"; "%[many]d changes" = "%d changes"; "%[none]d changes" = "%d changes"; 95 Localizable.strings
  19. /* The number of changes shown in the route description

    */ "%[one]d changes" = "%d остановка"; "%[some]d changes" = "%d остановки"; "%[many]d changes" = "%d остановок"; "%[none]d changes" = ""; 96 Localizable.strings
  20. NSString *YXPluralFormForRU(NSInteger n) { // One - 1, 21, 31,

    ... // Some - 2-4, 22-24, 32-34 ... // Many - 5-20, 25-30, ... NSInteger n10 = n % 10; if ((n10 == 1) && ((n == 1) || (n > 20))) { return @”[one]”; } else if ((n10 > 1) && (n10 < 5) && ((n > 20) || (n < 10))) { return @”[some]”; } else { return @”[many]”; } } 97
  21. NSString *pluralKey = NSLocalizedString( @"%[one, some, many, none]d changes", @"The

    number of changes shown in the route description"); NSString *pluralTransfers = YXLocalizedStringN(pluralKey, self.transfersCount); 98
  22. 105 Склеивание строк NSString *part1 = NSLocalizedString(@"People in the room",

    @"Part 1"); NSString *part2 = NSLocalizedString(@"%d", @"Part 2"); NSString *halfResult = [NSString stringWithFormat:@"%@: %@", part1, part2]; NSString *result = [NSString stringWithFormat:halfResult, 5]; ΁΍΁ɿ̑ਓ
  23. 109 Wincent Strings Utility “Merges, extracts and combines .string files

    (for incremental localization)” — http://wincent.com/a/products/wincent-strings-util/
  24. 111 Twine “String Management for iOS, Mac OS X, and

    Android Development” — http://www.mobiata.com/blog/2012/02/08/twine-string-management-ios-mac-os-x