Лучшая практика использования NSLocalizedString

141

Я (как и все остальные) использую NSLocalizedStringдля локализации своего приложения.

К сожалению, есть несколько "недостатков" (не обязательно по вине самого NSLocalizedString), в том числе

  • Нет автозаполнения для строк в Xcode. Это делает работу не только подверженной ошибкам, но и утомительной.
  • В конечном итоге вы можете переопределить строку просто потому, что не знали, что эквивалентная строка уже существует (например, «Пожалуйста, введите пароль» или «Сначала введите пароль»).
  • Как и в случае с автозаполнением, вам нужно «запомнить» / скопировать строки комментариев, иначе в результате genstringполучится несколько комментариев для одной строки.
  • Если вы хотите использовать genstringпосле того, как вы уже локализовали некоторые строки, вы должны быть осторожны, чтобы не потерять свои старые локализации.
  • Одинаковые строки разбросаны по всему вашему проекту. Например, вы использовали NSLocalizedString(@"Abort", @"Cancel action")везде, а затем Code Review просит вас переименовать строку в, NSLocalizedString(@"Cancel", @"Cancel action")чтобы сделать код более согласованным.

Что я делаю (и после некоторых поисков в SO я подумал, что многие люди делают это), так это иметь отдельный strings.hфайл, в котором я #defineвесь код локализации. Например

// In strings.h
#define NSLS_COMMON_CANCEL NSLocalizedString(@"Cancel", nil)
// Somewhere else
NSLog(@"%@", NSLS_COMMON_CANCEL);

По сути, это обеспечивает автозавершение кода, единое место для изменения имен переменных (так что больше нет необходимости в genstring) и уникальное ключевое слово для авторефакторинга. Однако это происходит за счет целого ряда #defineоператоров, которые по своей сути не структурированы (например, LocString.Common.Cancel или что-то в этом роде).

Итак, хотя это работает несколько нормально, мне было интересно, как вы, ребята, делаете это в своих проектах. Есть ли другие подходы к упрощению использования NSLocalizedString? Может быть, есть даже структура, которая инкапсулирует это?

JiaYow
источник
Я просто делаю это почти так же, как ты. Но я использую макрос NSLocalizedStringWithDefaultValue для создания разных строковых файлов для различных задач локализации (например, контроллеров, моделей и т. Д.) И для создания начального значения по умолчанию.
anka
Похоже, что экспорт xcode6 в локализацию не улавливает строки, которые определены как макросы в файле заголовка. Может ли кто-нибудь подтвердить или сказать мне, что мне может не хватать? Благодарность...!
Juddster
@Juddster, могу подтвердить, даже с новым редактором фонда -> Экспорт для локализации он не попадает в заголовочный файл
Red

Ответы:

100

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

Обновление файла строк

genstringsперезаписывает ваши строковые файлы, удаляя все ваши предыдущие переводы. Я написал update_strings.py, чтобы проанализировать старый файл строк, запустить genstringsи заполнить пробелы, чтобы вам не приходилось вручную восстанавливать существующие переводы. Сценарий пытается как можно точнее сопоставить существующие строковые файлы, чтобы избежать слишком большого различия при их обновлении.

Именование ваших струн

Если вы используете NSLocalizedStringкак рекламируется:

NSLocalizedString(@"Cancel or continue?", @"Cancel notice message when a download takes too long to proceed");

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

NSLocalizedString(@"DOWNLOAD_CANCEL_OR_CONTINUE", @"Cancel notice window title when a download takes too long to proceed");

Использование одной и той же строки в разных местах

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

Я надеюсь, что с этими советами вы будете более продуктивны при локализации какао!

ндфред
источник
Спасибо за ответ, обязательно посмотрю на ваш python-файл. Я согласен с вашим соглашением об именах. Недавно я разговаривал с некоторыми другими разработчиками iOS, и они рекомендовали использовать статические строки вместо макросов, что имеет смысл. Я проголосовал за ваш ответ, но подожду немного, прежде чем приму его, потому что решение все еще немного неуклюже. Может быть, придет что-нибудь получше. Еще раз спасибо!
JiaYow
Пожалуйста. Локализация - это утомительный процесс, и наличие правильных инструментов и рабочего процесса имеет огромное значение.
ndfred
18
Я никогда не понимал, почему функции локализации в стиле gettext используют в качестве ключа один из переводов. Что произойдет, если ваш исходный текст изменится? Ваши ключевые изменения и все ваши локализованные файлы используют старый текст в качестве своего ключа. Для меня это никогда не имело смысла. Я всегда использовал такие ключи, как home_button_text, поэтому они уникальны и никогда не меняются. Я также написал сценарий bash для анализа всех моих файлов Localizable.strings и создания файла класса со статическими методами, которые загрузят соответствующую строку. Это дает мне завершение кода. Однажды я смогу открыть это.
Майк Веллер
2
Я думаю, вы имеете в виду, что genstringsнет gestring.
hiroshi
1
Проверка времени компиляции @ndfred на то, что вы неправильно набрали строку, - самая большая победа. В любом случае нужно добавить немного больше кода. Кроме того, в случае рефакторинга статического анализа наличие символа значительно упростит задачу.
Аллен Зенг,
31

Что касается автозаполнения строк в Xcode, вы можете попробовать https://github.com/questbeat/Lin .

хироши
источник
3
Это действительно потрясающе. Не нужно создавать макросы.
Beau Nouvelle
1
страница не
найдена_
1
@Juanmi Спасибо, что упомянули мертвую ссылку. Я заменил ссылку на URL-адрес github.
Хироши,
24

Согласен с ndfred, но я хотел бы добавить следующее:

Второй параметр можно использовать как ... значение по умолчанию !!

(NSLocalizedStringWithDefaultValue не работает должным образом с genstring, поэтому я предложил это решение)

Вот моя пользовательская реализация, использующая NSLocalizedString, которая использует комментарий как значение по умолчанию:

1. В предварительно скомпилированном заголовке (файл .pch) переопределите макрос NSLocalizedString:

// cutom NSLocalizedString that use macro comment as default value
#import "LocalizationHandlerUtil.h"

#undef NSLocalizedString
#define NSLocalizedString(key,_comment) [[LocalizationHandlerUtil singleton] localizedString:key  comment:_comment]

2. создать класс для реализации обработчика локализации

#import "LocalizationHandlerUtil.h"

@implementation LocalizationHandlerUtil

static LocalizationHandlerUtil * singleton = nil;

+ (LocalizationHandlerUtil *)singleton
{
    return singleton;
}

__attribute__((constructor))
static void staticInit_singleton()
{
    singleton = [[LocalizationHandlerUtil alloc] init];
}

- (NSString *)localizedString:(NSString *)key comment:(NSString *)comment
{
    // default localized string loading
    NSString * localizedString = [[NSBundle mainBundle] localizedStringForKey:key value:key table:nil];

    // if (value == key) and comment is not nil -> returns comment
    if([localizedString isEqualToString:key] && comment !=nil)
        return comment;

    return localizedString;
}

@end

3. Используйте это!

Убедитесь, что вы добавили сценарий выполнения на этапах сборки приложения, чтобы файл Localizable.strings обновлялся при каждой сборке, т.е. новая локализованная строка будет добавлена ​​в файл Localized.strings:

Мой сценарий этапа сборки - это сценарий оболочки:

Shell: /bin/sh
Shell script content: find . -name \*.m | xargs genstrings -o MyClassesFolder

Итак, когда вы добавляете эту новую строку в свой код:

self.title = NSLocalizedString(@"view_settings_title", @"Settings");

Затем выполните сборку, ваш файл ./Localizable.scripts будет содержать эту новую строку:

/* Settings */
"view_settings_title" = "view_settings_title";

И поскольку ключ == значение для 'view_settings_title', пользовательский LocalizedStringHandler вернет комментарий, то есть «Настройки»

Вуаля :-)

Паскаль
источник
Получение ошибок ARC, неизвестный метод экземпляра для селектора 'localizedString: comment: :(
Mangesh
Я полагаю, это потому, что отсутствует LocalizationHandlerUtil.h. Я не могу найти код обратно ... Просто попробуйте создать файл заголовка LocalizationHandlerUtil.h, и все должно быть в порядке
Паскаль
Я создал файлы. Я думаю, это связано с проблемой пути к папке.
Mangesh
3

В Swift я использую следующее, например, для кнопки «Да» в этом случае:

NSLocalizedString("btn_yes", value: "Yes", comment: "Yes button")

Обратите внимание на использование в value:качестве текстового значения по умолчанию. Первый параметр служит идентификатором перевода. Преимущество использования value:параметра состоит в том, что текст по умолчанию можно изменить позже, но идентификатор перевода останется прежним. Файл Localizable.strings будет содержать"btn_yes" = "Yes";

Если value:параметр не использовался, то первый параметр будет использоваться как для идентификатора перевода, так и для текстового значения по умолчанию. Файл Localizable.strings будет содержать "Yes" = "Yes";. Такое управление файлами локализации кажется странным. Особенно, если переведенный текст длинный, то и идентификатор тоже длинный. Каждый раз при изменении любого символа текстового значения по умолчанию изменяется и идентификатор перевода. Это приводит к проблемам при использовании внешних систем перевода. Под изменением идентификатора перевода понимается добавление нового текста перевода, что не всегда может быть желательным.

Петрсын
источник
2

Я написал сценарий, помогающий поддерживать Localizable.strings на нескольких языках. Хотя это не помогает при автозаполнении, но помогает объединить файлы .strings с помощью команды:

merge_strings.rb ja.lproj/Localizable.strings en.lproj/Localizable.strings

Для получения дополнительной информации см. Https://github.com/hiroshi/merge_strings.

Надеюсь, некоторые из вас сочтут это полезным.

хироши
источник
2

Если кто-то ищет решение Swift. Вы можете проверить мое решение, которое я собрал здесь: SwiftyLocalization

Выполнив несколько шагов по настройке, вы получите очень гибкую локализацию в Google Spreadsheet (комментарий, пользовательский цвет, выделение, шрифт, несколько листов и т. Д.).

Вкратце, шаги: Таблица Google -> Файлы CSV -> Localizable.strings

Более того, он также генерирует Localizables.swift, структуру, которая действует как интерфейсы для извлечения и декодирования ключа для вас (хотя вы должны вручную указать способ декодирования String из ключа).

Почему это здорово?

  1. Вам больше не нужно иметь ключ в виде простой строки повсюду.
  2. Во время компиляции обнаруживаются неправильные ключи.
  3. Xcode может выполнять автозаполнение.

Хотя есть инструменты, которые могут автоматически заполнять ваш локализуемый ключ. Ссылка на реальную переменную гарантирует, что это всегда действительный ключ, иначе он не будет компилироваться.

// It's defined as computed static var, so it's up-to-date every time you call. 
// You can also have your custom retrieval method there.

button.setTitle(Localizables.login.button_title_login, forState: .Normal)

В проекте используется сценарий Google App Script для преобразования таблиц -> CSV и сценарий Python для преобразования файлов CSV -> Localizable.strings. Вы можете быстро взглянуть на этот примерный лист, чтобы узнать, что возможно.

aunnnn
источник
1

с iOS 7 и Xcode 5 вам следует избегать использования метода «Localization.strings» и использовать новый метод «базовой локализации». Есть несколько руководств, если вы используете Google для "базовой локализации"

Документ Apple: базовая локализация

Ронни Веберс
источник
да, Стив, это правильно. Кроме того, вам по-прежнему нужен метод файла .strings для любой динамически генерируемой строки. Но только для них предпочтительным методом Apple является базовая локализация.
Ронни Веберс
Ссылка на новый метод?
Hyperbole
1
Imo базовый метод локализации бесполезен. Вам все равно придется хранить другие файлы местоположения для динамических строк, и это позволяет вашим строкам распространяться по множеству файлов. Строки внутри перьев / раскадровок могут быть автоматически локализованы на ключи в Localizable.strings с помощью некоторых библиотек, таких как github.com/AliSoftware/OHAutoNIBi18n
Рафаэль Нобре
0
#define PBLocalizedString(key, val) \

[[NSBundle mainBundle] localizedStringForKey:(key) value:(val) table:nil]
баоцзифей
источник
0

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

Поскольку я использую свой собственный макрос поверх NSLocalizedString, пожалуйста, просмотрите и обновите скрипт перед использованием, поскольку я предположил для простоты, что nil используется в качестве второго параметра для NSLocalizedString. Часть, которую вы хотите изменить, это

NSLocalizedString\(@(".*?")\s*,\s*nil\) 

Просто замените его чем-то, что соответствует вашему макросу и использованию NSLocalizedString.

Вот сценарий, вам действительно нужна только часть 3. Остальное легче увидеть, откуда все это взялось:

// Part 1. Get keys from one of the Localizable.strings
perl -ne 'print "$1\n" if /^\s*(".+")\s*=/' myapp/fr.lproj/Localizable.strings

// Part 2. Get keys from the source code
grep -n -h -Eo -r  'NSLocalizedString\(@(".*?")\s*,\s*nil\)' ./ | perl -ne 'print "$1\n" if /NSLocalizedString\(@(".+")\s*,\s*nil\)/'

// Part 3. Get Part 1 and 2 together.

comm -2 -3 <(grep -n -h -Eo -r  'NSLocalizedString\(@(".*?")\s*,\s*nil\)' ./ | perl -ne 'print "$1\n" if /NSLocalizedString\(@(".+")\s*,\s*nil\)/' | sort | uniq) <(perl -ne 'print "$1\n" if /^\s*(".+")\s*=/' myapp/fr.lproj/Localizable.strings | sort) | uniq >> fr-localization-delta.txt

Выходной файл содержит ключи, которые были найдены в коде, но не в файле Localizable.strings. Вот пример:

"MPH"
"Map Direction"
"Max duration of a detailed recording, hours"
"Moving ..."
"My Track"
"New Trip"

Конечно можно отполировать еще, но думал поделится.

Станислав Двойченко
источник