Thực hành tốt nhất bằng NSLocalizedString


140

Tôi (giống như tất cả những người khác) đang sử dụng NSLocalizedStringđể bản địa hóa ứng dụng của mình.

Thật không may, có một số "nhược điểm" (không nhất thiết là lỗi của chính NSLocalizedString), bao gồm

  • Không tự động hoàn thành cho các chuỗi trong Xcode. Điều này làm cho công việc không chỉ dễ bị lỗi mà còn mệt mỏi.
  • Cuối cùng, bạn có thể xác định lại một chuỗi đơn giản vì bạn không biết một chuỗi tương đương đã tồn tại (nghĩa là "Vui lòng nhập mật khẩu" so với "Nhập mật khẩu trước")
  • Tương tự như vấn đề tự động hoàn thành, bạn cần "nhớ" / sao chép chuỗi nhận xét, nếu không genstringsẽ kết thúc với nhiều nhận xét cho một chuỗi
  • Nếu bạn muốn sử dụng genstringsau khi bạn đã bản địa hóa một số chuỗi, bạn phải cẩn thận để không bị mất bản địa hóa cũ.
  • Các chuỗi giống nhau nằm rải rác trong toàn bộ dự án của bạn. Ví dụ: bạn đã sử dụng NSLocalizedString(@"Abort", @"Cancel action")ở mọi nơi và sau đó Đánh giá mã yêu cầu bạn đổi tên chuỗi NSLocalizedString(@"Cancel", @"Cancel action")để làm cho mã phù hợp hơn.

Những gì tôi làm (và sau một số tìm kiếm trên SO tôi đã hình dung nhiều người làm điều này) là có một strings.htệp riêng biệt trong đó tôi có #definetất cả mã địa phương hóa. Ví dụ

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

Điều này về cơ bản cung cấp hoàn thành mã, một nơi duy nhất để thay đổi tên biến (vì vậy không cần chuỗi gen nữa) và một từ khóa duy nhất để tự động tái cấu trúc. Tuy nhiên, điều này phải trả giá bằng việc kết thúc bằng một loạt các #definecâu lệnh không có cấu trúc vốn có (ví dụ như LocString.Common.Cattery hoặc đại loại như thế).

Vì vậy, trong khi điều này hoạt động tốt, tôi đã tự hỏi làm thế nào các bạn làm điều đó trong các dự án của bạn. Có cách tiếp cận nào khác để đơn giản hóa việc sử dụng NSLocalizedString không? Có thể thậm chí có một khung đóng gói nó?


Tôi chỉ làm điều đó gần giống như bạn. Nhưng tôi đang sử dụng makro NSLocalizedStringWithDefaultValue để tạo các tệp chuỗi khác nhau cho các vấn đề nội địa hóa khác nhau (như bộ điều khiển, mô hình, v.v.) và để tạo giá trị mặc định ban đầu.
anka

Có vẻ như Xuất mã sang bản địa hóa của xcode6 không bắt được các chuỗi được xác định là macro trong tệp tiêu đề. Bất cứ ai có thể xác nhận hoặc cho tôi biết những gì tôi có thể thiếu? Cảm ơn...!
Juddster

@Juddster, có thể xác nhận, ngay cả với Trình chỉnh sửa quỹ mới-> Xuất để bản địa hóa, nó không được chọn trong tệp tiêu đề
Red

Câu trả lời:


100

NSLocalizedStringcó một vài hạn chế, nhưng nó rất quan trọng đối với ca cao đến mức không hợp lý khi viết mã tùy chỉnh để xử lý nội địa hóa, nghĩa là bạn sẽ phải sử dụng nó. Điều đó nói rằng, một công cụ nhỏ có thể giúp đỡ, đây là cách tôi tiến hành:

Cập nhật tệp chuỗi

genstringsghi đè lên các tệp chuỗi của bạn, loại bỏ tất cả các bản dịch trước đó của bạn. Tôi đã viết update_strings.py để phân tích tệp chuỗi cũ, chạy genstringsvà điền vào chỗ trống để bạn không phải khôi phục thủ công các bản dịch hiện có của mình. Tập lệnh cố gắng khớp các tệp chuỗi hiện có càng sát càng tốt để tránh có sự khác biệt quá lớn khi cập nhật chúng.

Đặt tên cho chuỗi của bạn

Nếu bạn sử dụng NSLocalizedStringnhư quảng cáo:

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

Cuối cùng, bạn có thể xác định cùng một chuỗi trong một phần khác của mã, điều này có thể mâu thuẫn vì cùng một thuật ngữ tiếng Anh có thể có ý nghĩa khác nhau trong các ngữ cảnh khác nhau ( OKCancelxuất hiện trong tâm trí). Đó là lý do tại sao tôi luôn sử dụng chuỗi all-caps vô nghĩa với tiền tố dành riêng cho mô-đun và mô tả rất chính xác:

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

Sử dụng cùng một chuỗi ở những nơi khác nhau

Nếu bạn sử dụng cùng một chuỗi nhiều lần, bạn có thể sử dụng một macro như bạn đã làm hoặc lưu nó dưới dạng một biến đối tượng trong trình điều khiển xem hoặc nguồn dữ liệu của bạn. Bằng cách này, bạn sẽ không phải lặp lại mô tả có thể trở nên cũ kỹ và không nhất quán giữa các trường hợp của cùng một địa phương hóa, điều này luôn gây nhầm lẫn. Vì các biến thể hiện là các ký hiệu, bạn sẽ có thể sử dụng tự động hoàn thành trên các bản dịch phổ biến nhất này và sử dụng các chuỗi "thủ công" cho các chuỗi cụ thể, điều này chỉ xảy ra một lần nào đó.

Tôi hy vọng bạn sẽ làm việc hiệu quả hơn với nội địa hóa ca cao với những lời khuyên này!


Cảm ơn câu trả lời của bạn, tôi chắc chắn sẽ xem tập tin python của bạn. Tôi đồng ý với quy ước đặt tên của bạn. Gần đây tôi đã nói chuyện với một số nhà phát triển iOS khác và họ khuyến nghị sử dụng chuỗi tĩnh thay vì macro, điều này có ý nghĩa. Tôi đã nêu lên câu trả lời của bạn, nhưng sẽ đợi một chút trước khi tôi chấp nhận nó, bởi vì giải pháp vẫn còn một chút vụng về. Có lẽ một cái gì đó tốt hơn đến cùng. Cảm ơn một lần nữa!
JiaYow

Bạn được chào đón. Bản địa hóa là một quá trình tẻ nhạt, có các công cụ và quy trình làm việc phù hợp tạo nên một thế giới khác biệt.
ndfred

17
Tôi chưa bao giờ hiểu tại sao các hàm nội địa hóa kiểu gettext sử dụng một trong các bản dịch làm khóa. Điều gì xảy ra nếu văn bản gốc của bạn thay đổi? Khóa của bạn thay đổi và tất cả các tệp được bản địa hóa của bạn đang sử dụng văn bản cũ cho khóa của họ. Nó chưa bao giờ có ý nghĩa với tôi. Tôi đã luôn sử dụng các khóa như "home_button bản" để chúng là duy nhất và không bao giờ thay đổi. Tôi cũng đã viết một tập lệnh bash để phân tích tất cả các tệp Localizable.strings của mình và tạo một tệp lớp với các phương thức tĩnh sẽ tải chuỗi thích hợp. Điều này cho tôi hoàn thành mã. Một ngày nào đó tôi có thể mở nguồn này.
Mike Weller

2
Tôi nghĩ bạn có nghĩa là genstringskhông gestring.
hiroshi

1
@ndfred biên dịch kiểm tra thời gian mà bạn chưa gõ sai chuỗi là chiến thắng lớn nhất. Đó là thêm một chút mã để thêm. Ngoài ra trong trường hợp tái cấu trúc, phân tích tĩnh, có một biểu tượng sẽ làm cho mọi thứ trở nên dễ dàng hơn.
Allen Zeng

31

Đối với tự động hoàn thành cho các chuỗi trong Xcode, bạn có thể thử https://github.com/questbeat/Lin .


3
Điều này thực sự khá tuyệt vời. Không cần tạo macro.
Beau Nouvelle

1
không tìm thấy trang_
Juanmi

1
@Juanmi Cảm ơn bạn đã đề cập đến liên kết chết. Tôi đã thay thế liên kết với url github.
hiroshi

24

Đồng ý với ndfred, nhưng tôi muốn thêm điều này:

Tham số thứ hai có thể được sử dụng làm ... giá trị mặc định !!

(NSLocalizedStringWithDefaultValue không hoạt động đúng với chuỗi gen, đó là lý do tại sao tôi đề xuất giải pháp này)

Dưới đây là triển khai Tùy chỉnh của tôi sử dụng NSLocalizedString sử dụng nhận xét làm giá trị mặc định:

1. Trong tiêu đề được biên dịch trước (tệp .pch), hãy xác định lại macro '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. tạo một lớp để thực hiện trình xử lý bản địa hóa

#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. Sử dụng nó!

Đảm bảo bạn thêm tập lệnh Run trong Giai đoạn xây dựng ứng dụng để tệp Localizable.strings của bạn sẽ được cập nhật tại mỗi bản dựng, tức là, chuỗi nội địa hóa mới sẽ được thêm vào trong tệp Localized.strings của bạn:

Script giai đoạn xây dựng của tôi là một kịch bản shell:

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

Vì vậy, khi bạn thêm dòng mới này vào mã của bạn:

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

Sau đó thực hiện xây dựng, tệp ./Localizable.scripts của bạn sẽ chứa dòng mới này:

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

Và vì key == value cho 'view_sinstall_title', nên LocalizedStringHandler tùy chỉnh sẽ trả về nhận xét, tức là 'Cài đặt "

Voilà :-)


Nhận lỗi ARC, Không có phương thức cá thể nào được biết cho bộ chọn 'localizedString: comment: :(
Mangesh

Tôi cho rằng đó là vì LocalizationHandlerUtil.h bị thiếu. Tôi không thể tìm lại mã ... Chỉ cần thử tạo tệp tiêu đề LocalizationHandlerUtil.h và nó sẽ ổn
Pascal

Tôi đã tạo các tập tin. Tôi nghĩ rằng đó là do vấn đề đường dẫn thư mục.
Mangesh

3

Trong Swift tôi đang sử dụng các mục sau, ví dụ: cho nút "Có" trong trường hợp này:

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

Lưu ý sử dụng value:cho giá trị văn bản mặc định. Tham số đầu tiên đóng vai trò là ID dịch. Ưu điểm của việc sử dụng value:tham số là văn bản mặc định có thể được thay đổi sau đó nhưng ID dịch vẫn giữ nguyên. Tệp Localizable.strings sẽ chứa"btn_yes" = "Yes";

Nếu value:tham số không được sử dụng thì tham số đầu tiên sẽ được sử dụng cho cả hai: cho ID dịch và cả giá trị văn bản mặc định. Tệp Localizable.strings sẽ chứa "Yes" = "Yes";. Kiểu quản lý tập tin nội địa hóa này có vẻ lạ. Đặc biệt nếu văn bản dịch dài thì ID cũng dài. Bất cứ khi nào bất kỳ ký tự nào của giá trị văn bản mặc định được thay đổi, thì ID dịch cũng sẽ được thay đổi. Điều này dẫn đến các vấn đề khi hệ thống dịch thuật bên ngoài được sử dụng. Thay đổi ID dịch được hiểu là thêm văn bản dịch mới, có thể không phải lúc nào cũng mong muốn.


2

Tôi đã viết một tập lệnh để giúp duy trì Localizable.strings bằng nhiều ngôn ngữ. Mặc dù nó không giúp tự động hoàn thành nhưng nó giúp hợp nhất các tệp .strings bằng lệnh:

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

Để biết thêm thông tin, hãy xem: https://github.com/hiroshi/merge_strings

Một số bạn thấy nó hữu ích tôi hy vọng.


2

Nếu bất cứ ai tìm kiếm một giải pháp Swift. Bạn có thể muốn kiểm tra giải pháp của tôi, tôi kết hợp ở đây: SwiftyLocalization

Với một vài bước để thiết lập, bạn sẽ có một bản địa hóa rất linh hoạt trong Bảng tính Google (nhận xét, màu tùy chỉnh, tô sáng, phông chữ, nhiều trang tính, v.v.).

Tóm lại, các bước là: Bảng tính Google -> tệp CSV -> Localizable.strings

Hơn nữa, nó cũng tạo ra Localizables.swift, một cấu trúc hoạt động như giao diện để truy xuất và giải mã khóa cho bạn (Bạn phải chỉ định một cách thủ công cách giải mã Chuỗi từ khóa).

Tại sao điều này là tuyệt vời?

  1. Bạn không còn cần phải có một khóa như một chuỗi đơn giản ở khắp mọi nơi.
  2. Các khóa sai được phát hiện tại thời điểm biên dịch.
  3. Xcode có thể tự động hoàn thành.

Trong khi có các công cụ có thể tự động hoàn thành khóa bản địa hóa của bạn. Tham chiếu đến một biến thực sẽ đảm bảo rằng nó luôn là một khóa hợp lệ, nếu không nó sẽ không được biên dịch.

// 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)

Dự án sử dụng Tập lệnh ứng dụng của Google để chuyển đổi Trang tính -> CSV và tập lệnh Python để chuyển đổi tệp CSV -> Localizable.strings Bạn có thể xem nhanh bảng mẫu này để biết những gì có thể.


1

với iOS 7 & Xcode 5, bạn nên tránh sử dụng phương thức 'Localization.strings' và sử dụng phương thức 'bản địa hóa cơ sở' mới. Có một số hướng dẫn xung quanh nếu bạn google cho 'nội địa hóa'

Apple doc: Bản địa hóa cơ sở


vâng, Steve đúng vậy Ngoài ra, bạn vẫn cần phương thức tệp .strings cho bất kỳ chuỗi được tạo động nào. Nhưng chỉ đối với những điều này, phương pháp ưa thích của Apple là nội địa hóa.
Ronny Webers

Liên kết với phương pháp mới?
Hyperbole

1
Imo phương pháp bản địa hóa cơ sở là vô giá trị. Bạn vẫn phải giữ các tệp vị trí khác cho các chuỗi động và nó giữ cho các chuỗi của bạn trải đều qua rất nhiều tệp. Các chuỗi bên trong Nibs / Storyboards có thể được định vị tự động thành các khóa trong Localizable.strings với một số Lib như github.com/AliSoftware/OHAutoNIBi18n
Rafael Nobre

0
#define PBLocalizedString(key, val) \

[[NSBundle mainBundle] localizedStringForKey:(key) value:(val) table:nil]

0

Bản thân tôi, tôi thường mang theo mã hóa, quên đặt các mục vào tệp .strings. Vì vậy, tôi có các tập lệnh trợ giúp để tìm những gì tôi nợ để đưa lại vào các tập tin .strings và dịch.

Khi tôi sử dụng macro của riêng mình trên NSLocalizedString, vui lòng xem lại và cập nhật tập lệnh trước khi sử dụng vì tôi cho rằng đơn giản là nil được sử dụng làm thông số thứ hai cho NSLocalizedString. Phần bạn muốn thay đổi là

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

Chỉ cần thay thế nó bằng một cái gì đó phù hợp với việc sử dụng macro và NSLocalizedString của bạn.

Ở đây có kịch bản, bạn chỉ cần Phần 3 thực sự. Phần còn lại là để thấy dễ dàng hơn tất cả đến từ đâu:

// 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

Tệp đầu ra chứa các khóa được tìm thấy trong mã, nhưng không có trong tệp Localizable.strings. Đây là một mẫu:

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

Chắc chắn có thể được đánh bóng nhiều hơn, nhưng nghĩ rằng tôi muốn chia sẻ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.