Your SlideShare is downloading. ×
Андрей Субботин "Автоматизация локализации iOS-приложений"
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Introducing the official SlideShare app

Stunning, full-screen experience for iPhone and Android

Text the download link to your phone

Standard text messaging rates apply

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

1,266
views

Published on

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

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

Были рассмотрены все базовые инструментаы локализации (genstrings, ibtool) и способы их использования.


0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
1,266
On Slideshare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
6
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Автоматизация локализации iOS-приложений Андрей Субботин eploko@yandex-team.ru @eploko 1
  • 2. Зачем? Что это?Как это? Кто?Яндекс. СтрашноеПолезное Забавное 2
  • 3. Зачем нужналокализация? 3
  • 4. Cant read,wont buy. 4
  • 5. 52,4% не покупают продукт на чужом языке. 60% — для Франции, Японии и России. 89,3% — если английский знают плохо. 5
  • 6. http://bit.ly/whylocalize 6
  • 7. Интернационализация i18n = подготовка продукта к локализации 7
  • 8. Локализация L10n= адаптация продукта к конкретному языку и местности 8
  • 9. Понятный язык интерфейса. 9
  • 10. Дата и время в привычном формате. 10
  • 11. Корректная сортировка списков. 11
  • 12. Поддержка местных единиц измерения. 12
  • 13. Правильное форматирование чисел. 13
  • 14. Что локализуется в приложении? 14
  • 15. Текстовые строки.15
  • 16. XIB-файлы.16
  • 17. Изображения, аудио.17
  • 18. Как приложение подгружает ресурсы? 18
  • 19. 19
  • 20. en.lproj 20
  • 21. ru.lproj 21
  • 22. Подготовка строк к локализации 22
  • 23. NSLocalizedString = ваш друг! 23
  • 24. NSLocalizedString(@"key",@"translator comment") 24
  • 25. NSLog(NSLocalizedString(@"Some sampletext", @"A text string to be output to thelogs.")); 25
  • 26. 2012-03-06 08:10:05.433L10nSample[15433:f803] Some sampletext2012-03-06 08:11:02.117L10nSample[15438:f903] Некийпримерный текст 26
  • 27. NSFormatter = тоже ваш друг!→ Data Formatting Guide 27
  • 28. Выделение строк 28
  • 29. *.m → Localizable.strings$ genstrings *.m -o Resources/en.lproj 29
  • 30. en.lproj/Localizable.strings/* A text string to be output to the logs. */"Some sample text" = "Some sample text";ru.lproj/Localizable.strings/* A text string to be output to the logs. */"Some sample text" = "Некий примерный текст"; 30
  • 31. Выделение строк из XIB 31
  • 32. *.xib → *.strings$ ibtool --export-strings-file en.lproj/ViewController.strings en.lproj/ViewController.xib 32
  • 33. en.lproj/ViewController.strings/* Class = "IBUIButton"; normalTitle = "Welcome!";ObjectID = "8"; */"8.normalTitle" = "Welcome!"; 33
  • 34. Вмерживание переводов обратно 34
  • 35. *.strings → *.xib$ ibtool --import-strings-file en.lproj/ViewController.strings en.lproj/ViewController.xib --write en.lproj/ViewController.xib 35
  • 36. Инкрементальноеобновление XIBов 36
  • 37. Создали en.XIB. Локализовали en.XIB → ru.XIB.Добавили новую кнопку в en.XIB. А теперь что?! 37
  • 38. en.xib → ru.xib$ ibtool--previous-file en.lproj/Window.old.xib--incremental-file ru.lproj/Window.old.xib--strings-file ru.lproj/Window.strings--localize-incremental--write ru.lproj/Window.xiben.lproj/Window.new.xib 38
  • 39. en.xib → ru.xib$ ibtool--previous-file en.lproj/Window.old.xib--incremental-file ru.lproj/Window.old.xib--strings-file ru.lproj/Window.strings--localize-incremental--write ru.lproj/Window.xiben.lproj/Window.new.xib 39
  • 40. en.xib → ru.xib$ ibtool--previous-file en.lproj/Window.old.xib--incremental-file ru.lproj/Window.old.xib--strings-file ru.lproj/Window.strings--localize-incremental--write ru.lproj/Window.xiben.lproj/Window.new.xib 40
  • 41. en.xib → ru.xib$ ibtool--previous-file en.lproj/Window.old.xib--incremental-file ru.lproj/Window.old.xib--strings-file ru.lproj/Window.strings--localize-incremental--write ru.lproj/Window.xiben.lproj/Window.new.xib 41
  • 42. en.xib → ru.xib$ ibtool--previous-file en.lproj/Window.old.xib--incremental-file ru.lproj/Window.old.xib--strings-file ru.lproj/Window.strings--localize-incremental--write ru.lproj/Window.xiben.lproj/Window.new.xib 42
  • 43. en.xib → ru.xib$ ibtool--previous-file en.lproj/Window.old.xib--incremental-file ru.lproj/Window.old.xib--strings-file ru.lproj/Window.strings--localize-incremental--write ru.lproj/Window.xiben.lproj/Window.new.xib 43
  • 44. en.xib → ru.xib$ ibtool--previous-file en.lproj/Window.old.xib--incremental-file ru.lproj/Window.old.xib--strings-file ru.lproj/Window.strings--localize-incremental--write ru.lproj/Window.xiben.lproj/Window.new.xib 44
  • 45. Храните предыдущие версии XIB файлов.45
  • 46. Не правьте рукамилокализованные XIB файлы. 46
  • 47. Переводчики 47
  • 48. Переводчикипонимают английский. 48
  • 49. Переводчикине всегда понимают русский. 49
  • 50. Переводчики не используют Xcodeи не редактируют XIB. 50
  • 51. ПереводчикиНе знают контекста перевода без вашей помощи. 51
  • 52. Как ониработают? 52
  • 53. Вместе с вами, но редко.53
  • 54. Очень часто по e-mail.54
  • 55. translations.launchpad.net +1 к карме 55
  • 56. Tanker= web-сервис= API для загрузки ивыгрузки переводов 56
  • 57. Babelfish Yoda 57
  • 58. Babelyoda избавляет отрутинной ручной работысводит количество багов при локализации к минимуму 58
  • 59. генерирует .strings из кода и XIB’ов загружает .strings в Tanker забирает из Tanker’а свежие переводы обновляет XIB файлы аккуратно все коммитит в git PROFIT!!
  • 60. Babelyoda= библиотека для работы с .strings, genstrings и ibtool 60
  • 61. Babelfile ...по аналогии сMakefile, Gemfile, Rakefile и т.п.= единое место конфигурации. 61
  • 62. 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
  • 63. 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
  • 64. 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
  • 65. 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
  • 66. 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
  • 67. rake babel yoda$One command to rule them all! 67
  • 68. $ rake -Trake babelyodarake babelyoda:create_keysetsrake babelyoda:drop_empty_stringsrake babelyoda:drop_orphan_keysrake babelyoda:drop_orphan_keysetsrake babelyoda:extractrake babelyoda:extract_stringsrake babelyoda:extract_xib_stringsrake babelyoda:fetch_stringsrake babelyoda:initBabelfilerake babelyoda:localize_xibsrake babelyoda:pullrake babelyoda:pushrake babelyoda:remote:drop_keysetsrake babelyoda:remote:listrake babelyoda:verify 68
  • 69. yxbuildkit-prebuild.sh#!/bin/bashfunction 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
  • 70. Available on GitHub!https://github.com/eploko/babelyoda 70
  • 71. Плюрализациях орр ор стори 71
  • 72. I scanned 12directories. 72
  • 73. NSLog(@"I scanned %g directories.", directoryCount); 73
  • 74. I scanned 1directories. 74
  • 75. NSLog(@"I scanned %g %@.", directoryCount, directoryCount == 1 ? @"directory" : @"directories", ); 75
  • 76. I scanned 1directory. 76
  • 77. NSLog( NSLocalizedString(@"I scanned %g %@.", @”Text to show the number ofdirectories scanned”), dirScanCount, dirScanCount == 1 ? NSLocalizedString(@"directory", @”Single directory”) : NSLocalizedString(@"directories", @”Plural directories”) ); 77
  • 78. Как это видитпереводчик? 78
  • 79. "I scanned %g %@.""directories""directory" 79
  • 80. "Я просканировал %g %@.""каталоги""папка" 80
  • 81. Я отсканировал 1папка. 81
  • 82. Я отсканировал 5каталоги. 82
  • 83. NSLog(dirScanCount == 1 ?NSLocalizedString("I scanned %g directory.", @”Blah”) :NSLocalizedString("I scanned %g directories.", @”Blah”),dirScanCount ); 83
  • 84. “It is more complicated thanyou think.”— The Eighth Networking Truth, from RFC 1925 84
  • 85. NSString *pluralTransfers =NSLocalizedString(@"%d changes",@"The number of changes shown in the route description"); 85
  • 86. 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
  • 87. 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
  • 88. Как это видитпереводчик? 88
  • 89. "%d change""%d changes""%d changes""%d changes" 89
  • 90. 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
  • 91. Как это видитпереводчик? 91
  • 92. "NumberChanges0""NumberChanges1""NumberChanges2""NumberChanges3" 92
  • 93. genstrings “магия” 93
  • 94. NSLocalizedString(@"%[one, some, many, none]d changes", @"The number of changes shown in the route description"); 94
  • 95. Localizable.strings/* 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
  • 96. Localizable.strings/* 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
  • 97. 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
  • 98. 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
  • 99. "%[one, some, many, none]d changes" "%[some]d changes"
  • 100. “Хитрости” 100
  • 101. Английский текст в качестве ключаNSLocalizedString(@"Tap Here", @"Action button title");NSLocalizedString(@"TapButtonTitle", @"Action button title"); 101
  • 102. Английский текст в качестве ключа WelcomeButtonTitle WelcomeTitle ButtonTitleWelcome WelcomeTITLE WelcomeBtnTitle 102
  • 103. Различные контекстыEdit = ПравитьEdit = ИзменитьEdit = Переименовать 103
  • 104. Различные контекстыNSLocalizedStringFromTable(<#key#>, <#tbl#>, <#comment#>)NSLocalizedStringFromTable(@”Edit”,@”Common”,@”Blah”)NSLocalizedStringFromTable(@”Edit”,@”Buttons”,@”Blah”) 104
  • 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]; へやへ:5人 105
  • 106. Склеивание строкNSLocalizedString( @"People in the room: %[one, some, many, none]d", @"Blah blah"); へやへ:5人 106
  • 107. Полезные проекты107
  • 108. Linguan108
  • 109. Wincent Strings Utility“Merges, extracts andcombines .string files (forincremental localization)”— http://wincent.com/a/products/wincent-strings-util/ 109
  • 110. genstrings2“40x faster than genstrings”— http://www.cocoanetics.com/2012/01/genstrings2/ 110
  • 111. Twine“String Management for iOS,Mac OS X, and AndroidDevelopment”— http://www.mobiata.com/blog/2012/02/08/twine-string-management-ios-mac-os-x 111
  • 112. WTF!? 112
  • 113. WTF!?113
  • 114. WTF!?114
  • 115. WTF!?115
  • 116. WTF!? 116
  • 117. WTF!? WTF!?WTF!? 117
  • 118. WTF!? 118
  • 119. WTF!?WTF!?WTF!?WTF!? WTF!? 119
  • 120. WTF!? WTF!? 120
  • 121. Andrey SubbotinВопросы? :-) eploko@yandex-team.ru @eploko