Localization (bản địa hoá) là quá trình làm cho app của bạn sẵn sàng hoạt động tốt ở các locale (hay quốc gia) khác nhau.
Như bạn có thể biết, không phải ai cũng dùng cùng ký tự cho dấu thập phân hay cho dấu phân cách hàng nghìn, hay cho đơn vị tiền tệ.
Các locale này có tên, và bạn có thể chọn một cái để dùng. Ví dụ, locale Mỹ có thể viết một con số như:
100,000.00
Còn ở Brazil, cùng số đó có thể được viết với dấu phẩy và dấu chấm đổi chỗ:
100.000,00
Chuyện này dễ dàng hơn khi bạn viết code sao cho dễ chuyển sang các quốc tịch khác!
Ừ, kiểu kiểu. Hoá ra C chỉ có đúng một locale sẵn, và nó bị giới hạn. Spec chừa ra khá nhiều chỗ mập mờ ở đây; khó mà thật sự portable hoàn toàn.
Nhưng ta sẽ cố gắng hết sức!
Với các lời gọi này, include <locale.h>.
Về cơ bản chỉ có một việc bạn có thể làm portable ở đây khi khai báo một locale cụ thể. Đây rất có thể là điều bạn muốn làm nếu định đụng tới locale:
setlocale(LC_ALL, ""); // Use this environment's locale for everythingBạn sẽ muốn gọi nó để chương trình được khởi tạo với locale hiện tại của bạn.
Đi vào chi tiết hơn, có một chuyện nữa bạn làm được mà vẫn portable:
setlocale(LC_ALL, "C"); // Use the default C localenhưng cái đó được gọi mặc định mỗi lần chương trình của bạn khởi chạy, nên không mấy khi cần tự gọi.
Trong chuỗi thứ hai đó, bạn có thể chỉ định bất kỳ locale nào được hệ thống của bạn hỗ trợ. Chuyện này hoàn toàn phụ thuộc hệ, nên sẽ khác nhau. Trên hệ của tôi, tôi có thể chỉ định cái này:
setlocale(LC_ALL, "en_US.UTF-8"); // Non-portable!Và cái đó sẽ chạy. Nhưng nó chỉ portable sang các hệ có đúng cùng tên đó cho đúng locale đó, và bạn không thể bảo đảm được.
Bằng cách truyền chuỗi rỗng ("") làm đối số thứ hai, bạn đang nói với C, “Này, tự tìm xem locale hiện tại trên hệ này là gì để tôi khỏi phải nói cho.”
Vì di chuyển mấy tờ giấy xanh hứa hẹn là chìa khoá tới hạnh phúc156, hãy nói về locale cho tiền tệ. Khi bạn viết code portable, bạn phải biết phải gõ gì cho tiền mặt, đúng không? Dù nó là “$”, “€”, “¥”, hay “£”.
Làm sao viết code đó mà không phát điên? May thay, khi bạn gọi setlocale(LC_ALL, ""), bạn có thể tra mấy cái này bằng một lời gọi localeconv():
struct lconv *x = localeconv();Hàm này trả về pointer tới một struct lconv được cấp phát tĩnh có mọi thông tin ngon lành bạn đang tìm.
Đây là các field của struct lconv và nghĩa của chúng.
Trước hết, vài quy ước. _p_ nghĩa là “positive” (dương), _n_ nghĩa là “negative” (âm), và int_ nghĩa là “international” (quốc tế). Dù nhiều cái có kiểu char hoặc char*, phần lớn (hoặc các chuỗi chúng trỏ tới) thật ra được xem như số nguyên157.
Trước khi đi tiếp, biết rằng CHAR_MAX (từ <limits.h>) là giá trị tối đa lưu được trong một char. Và nhiều giá trị char dưới đây dùng nó để cho biết giá trị không có ở locale đó.
| Field | Mô tả |
|---|---|
char *mon_decimal_point |
Ký tự dấu thập phân cho tiền, ví dụ ".". |
char *mon_thousands_sep |
Ký tự phân cách hàng nghìn cho tiền, ví dụ ",". |
char *mon_grouping |
Mô tả cách nhóm cho tiền (xem bên dưới). |
char *positive_sign |
Dấu dương cho tiền, ví dụ "+" hoặc "". |
char *negative_sign |
Dấu âm cho tiền, ví dụ "-". |
char *currency_symbol |
Ký hiệu tiền tệ, ví dụ "$". |
char frac_digits |
Khi in lượng tiền, in bao nhiêu chữ số sau dấu thập phân, ví dụ 2. |
char p_cs_precedes |
1 nếu currency_symbol đứng trước giá trị cho lượng tiền không âm, 0 nếu sau. |
char n_cs_precedes |
1 nếu currency_symbol đứng trước giá trị cho lượng tiền âm, 0 nếu sau. |
char p_sep_by_space |
Quy định cách ngăn cách currency symbol khỏi giá trị cho lượng không âm (xem bên dưới). |
char n_sep_by_space |
Quy định cách ngăn cách currency symbol khỏi giá trị cho lượng âm (xem bên dưới). |
char p_sign_posn |
Quy định vị trí của positive_sign cho giá trị không âm. |
char n_sign_posn |
Quy định vị trí của positive_sign cho giá trị âm. |
char *int_curr_symbol |
Ký hiệu tiền tệ quốc tế, ví dụ "USD ". |
char int_frac_digits |
Giá trị quốc tế cho frac_digits. |
char int_p_cs_precedes |
Giá trị quốc tế cho p_cs_precedes. |
char int_n_cs_precedes |
Giá trị quốc tế cho n_cs_precedes. |
char int_p_sep_by_space |
Giá trị quốc tế cho p_sep_by_space. |
char int_n_sep_by_space |
Giá trị quốc tế cho n_sep_by_space. |
char int_p_sign_posn |
Giá trị quốc tế cho p_sign_posn. |
char int_n_sign_posn |
Giá trị quốc tế cho n_sign_posn. |
Được rồi, cái này hơi chập cheng. mon_grouping là char*, nên bạn có thể nghĩ nó là chuỗi. Nhưng trong trường hợp này, không, nó thực ra là mảng các char. Nó luôn phải kết thúc bằng 0 hoặc CHAR_MAX.
Các giá trị này mô tả cách nhóm các tập số trong tiền tệ ở phía trái dấu thập phân (phần nguyên).
Ví dụ, ta có thể có:
2 1 0
--- --- ---
$100,000,000.00Đây là các nhóm ba chữ số. Nhóm 0 (ngay bên trái dấu thập phân) có 3 chữ số. Nhóm 1 (nhóm kế tiếp về trái) có 3 chữ số, và nhóm cuối cũng có 3.
Vậy ta có thể mô tả các nhóm này, từ phải (dấu thập phân) sang trái bằng một loạt số nguyên đại diện cho kích thước nhóm:
3 3 3Và chừng đó sẽ ổn với giá trị tới $100,000,000.
Nhưng lỡ ta có nhiều hơn? Ta có thể cứ thêm 3…
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3nhưng kiểu đó điên rồ. May thay, ta có thể chỉ định 0 để báo rằng kích thước nhóm trước được lặp lại:
3 0Nghĩa là lặp mỗi 3. Cái đó sẽ xử được $100, $1,000, $10,000, $10,000,000, $100,000,000,000, và cứ thế.
Bạn có thể chính thức phát khùng với mấy cái này để chỉ ra vài kiểu nhóm kỳ cục.
Ví dụ:
4 3 2 1 0sẽ cho ra:
$1,0,0,0,0,00,000,0000.00Một giá trị nữa có thể xuất hiện là CHAR_MAX. Cái này báo rằng không còn nhóm nào nữa, và có thể xuất hiện ở bất kỳ chỗ nào trong mảng, kể cả giá trị đầu.
3 2 CHAR_MAXsẽ cho ra:
100000000,00,000.00chẳng hạn.
Và chỉ cần có CHAR_MAX ở vị trí đầu của mảng là báo cho bạn biết không có nhóm nào hết.
Mọi biến thể sep_by_space xử lý khoảng trắng quanh ký hiệu tiền tệ. Giá trị hợp lệ là:
| Giá trị | Mô tả |
|---|---|
0 |
Không có khoảng trắng giữa ký hiệu tiền tệ và giá trị. |
1 |
Tách ký hiệu tiền tệ (và dấu, nếu có) khỏi giá trị bằng một khoảng trắng. |
2 |
Tách ký hiệu dấu khỏi ký hiệu tiền tệ (nếu kề nhau) bằng khoảng trắng, ngược lại tách ký hiệu dấu khỏi giá trị bằng khoảng trắng. |
Các biến thể sign_posn được quyết định bởi các giá trị sau:
| Giá trị | Mô tả |
|---|---|
0 |
Bọc giá trị và ký hiệu tiền tệ bằng cặp ngoặc. |
1 |
Đặt chuỗi dấu trước ký hiệu tiền tệ và giá trị. |
2 |
Đặt chuỗi dấu sau ký hiệu tiền tệ và giá trị. |
3 |
Đặt chuỗi dấu ngay trước ký hiệu tiền tệ. |
4 |
Đặt chuỗi dấu ngay sau ký hiệu tiền tệ. |
Khi tôi lấy các giá trị trên hệ của mình, đây là thứ tôi thấy (chuỗi grouping hiển thị dưới dạng các giá trị byte riêng):
mon_decimal_point = "."
mon_thousands_sep = ","
mon_grouping = 3 3 0
positive_sign = ""
negative_sign = "-"
currency_symbol = "$"
frac_digits = 2
p_cs_precedes = 1
n_cs_precedes = 1
p_sep_by_space = 0
n_sep_by_space = 0
p_sign_posn = 1
n_sign_posn = 1
int_curr_symbol = "USD "
int_frac_digits = 2
int_p_cs_precedes = 1
int_n_cs_precedes = 1
int_p_sep_by_space = 1
int_n_sep_by_space = 1
int_p_sign_posn = 1
int_n_sign_posn = 1Để ý ta đã truyền macro LC_ALL cho setlocale() ở trên, chuyện này gợi ý có thể có biến thể khác cho phép bạn chính xác hơn về phần nào của locale bạn đang đặt.
Hãy xem các giá trị bạn có thể thấy cho mấy cái này:
| Macro | Mô tả |
|---|---|
LC_ALL |
Đặt tất cả những cái dưới đây về locale đã cho. |
LC_COLLATE |
Kiểm soát hành vi của hàm strcoll() và strxfrm(). |
LC_CTYPE |
Kiểm soát hành vi của các hàm xử lý ký tự158. |
LC_MONETARY |
Kiểm soát giá trị mà localeconv() trả về. |
LC_NUMERIC |
Kiểm soát dấu thập phân cho họ hàm printf(). |
LC_TIME |
Kiểm soát định dạng thời gian cho các hàm in thời gian và ngày strftime() và wcsftime(). |
Khá phổ biến thấy LC_ALL được đặt, nhưng, này, ít nhất bạn có lựa chọn.
Cũng nên nói LC_CTYPE là một trong những cái lớn vì nó gắn với wide character, một mớ bùng nhùng ta sẽ nói sau.