More Related Content Similar to Андрей Субботин "Автоматизация локализации iOS-приложений" (20) Андрей Субботин "Автоматизация локализации iOS-приложений"2. Зачем? Что это?
Как это? Кто?
Яндекс. Страшное
Полезное Забавное
2
5. 52,4% не покупают продукт на чужом
языке.
60% — для Франции, Японии и
России.
89,3% — если английский знают
плохо.
5
8. Локализация L10n
= адаптация продукта к
конкретному языку и
местности
8
27. NSFormatter
= тоже ваш друг!
→ Data Formatting Guide
27
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
32. *.xib → *.strings
$ ibtool --export-strings-file
en.lproj/ViewController.strings
en.lproj/ViewController.xib
32
35. *.strings → *.xib
$ ibtool --import-strings-file
en.lproj/ViewController.strings
en.lproj/ViewController.xib
--write en.lproj/ViewController.xib
35
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.xib
en.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.xib
en.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.xib
en.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.xib
en.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.xib
en.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.xib
en.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.xib
en.lproj/Window.new.xib
44
58. Babelyoda
избавляет от
рутинной ручной работы
сводит количество багов
при локализации к
минимуму
58
59. генерирует .strings
из кода и XIB’ов загружает .strings
в Tanker
забирает из Tanker’а
свежие переводы
обновляет
XIB файлы
аккуратно все
коммитит в git
PROFIT!!
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
68. $ 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
69. yxbuildkit-prebuild.sh
#!/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
75. NSLog(@"I scanned %g %@.",
directoryCount,
directoryCount == 1 ?
@"directory" : @"directories",
);
75
77. 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
83. NSLog(
dirScanCount == 1 ?
NSLocalizedString("I scanned %g directory.", @”Blah”) :
NSLocalizedString("I scanned %g directories.", @”Blah”),
dirScanCount );
83
84. “It is more complicated than
you think.”
— The Eighth Networking Truth, from RFC 1925
84
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
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
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
101. Английский текст в качестве ключа
NSLocalizedString(@"Tap Here", @"Action button title");
NSLocalizedString(@"TapButtonTitle", @"Action button title");
101
102. Английский текст в качестве ключа
WelcomeButtonTitle
WelcomeTitle
ButtonTitleWelcome
WelcomeTITLE
WelcomeBtnTitle
102
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
109. Wincent Strings Utility
“Merges, extracts and
combines .string files (for
incremental localization)”
— http://wincent.com/a/products/wincent-strings-util/
109
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
111
117. WTF!?
WTF!?
WTF!?
117