Tôi đã không thấy bất kỳ đề cập nào trong các câu trả lời hiện có về các vấn đề liên quan đến các điểm mã máy bay hoặc quốc tế hóa. Cấm Uppercase không có nghĩa là điều tương tự trong mọi ngôn ngữ sử dụng một tập lệnh nhất định.
Ban đầu tôi không thấy bất kỳ câu trả lời nào giải quyết các vấn đề liên quan đến các điểm mã máy bay. Có một cái , nhưng nó hơi bị chôn vùi (như cái này sẽ thế, tôi đoán vậy!)
Hầu hết các chức năng được đề xuất trông như thế này:
function capitalizeFirstLetter(str) {
return str[0].toUpperCase() + str.slice(1);
}
Tuy nhiên, một số ký tự được đặt bên ngoài BMP (mặt phẳng đa ngôn ngữ cơ bản, mã điểm U + 0 đến U + FFFF). Ví dụ: lấy văn bản Deseret này:
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉"); // "𐐶𐐲𐑌𐐼𐐲𐑉"
Ký tự đầu tiên ở đây không viết hoa được vì các thuộc tính được lập chỉ mục theo chuỗi của các chuỗi không truy cập vào các ký tự của Wikipedia hoặc các điểm mã *. Họ truy cập các đơn vị mã UTF-16. Điều này cũng đúng khi cắt - giá trị chỉ mục trỏ vào các đơn vị mã.
Điều xảy ra là các đơn vị mã UTF-16 là 1: 1 với các điểm mã USV trong hai phạm vi, bao gồm U + 0 đến U + D7FF và U + E000 đến U + FFFF. Hầu hết các nhân vật vỏ bọc rơi vào hai phạm vi đó, nhưng không phải tất cả.
Từ ES2015 trở đi, việc giải quyết việc này trở nên dễ dàng hơn một chút. String.prototype[@@iterator]
mang lại chuỗi tương ứng với các điểm mã **. Vì vậy, ví dụ, chúng ta có thể làm điều này:
function capitalizeFirstLetter([ first, ...rest ]) {
return [ first.toUpperCase(), ...rest ].join('');
}
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Đối với các chuỗi dài hơn, điều này có thể không hiệu quả khủng khiếp *** - chúng tôi thực sự không cần phải lặp lại phần còn lại. Chúng ta có thể sử dụng String.prototype.codePointAt
để nhận được chữ cái đầu tiên (có thể), nhưng chúng ta vẫn cần xác định nơi lát cắt sẽ bắt đầu. Một cách để tránh lặp lại phần còn lại sẽ là kiểm tra xem điểm mã đầu tiên có nằm ngoài BMP hay không; nếu không, lát cắt bắt đầu từ 1 và nếu có, lát cắt bắt đầu từ 2.
function capitalizeFirstLetter(str) {
const firstCP = str.codePointAt(0);
const index = firstCP > 0xFFFF ? 2 : 1;
return String.fromCodePoint(firstCP).toUpperCase() + str.slice(index);
}
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Bạn có thể sử dụng toán bitwise thay vì > 0xFFFF
ở đó, nhưng có lẽ dễ hiểu hơn theo cách này và sẽ đạt được điều tương tự.
Chúng tôi cũng có thể làm cho công việc này hoạt động trong ES5 trở xuống bằng cách đưa logic đó đi xa hơn một chút nếu cần thiết. Không có phương thức nội tại nào trong ES5 để làm việc với các điểm mã, vì vậy chúng tôi phải kiểm tra thủ công xem đơn vị mã đầu tiên có phải là đại diện thay thế ****:
function capitalizeFirstLetter(str) {
var firstCodeUnit = str[0];
if (firstCodeUnit < '\uD800' || firstCodeUnit > '\uDFFF') {
return str[0].toUpperCase() + str.slice(1);
}
return str.slice(0, 2).toUpperCase() + str.slice(2);
}
capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"
Lúc đầu tôi cũng đề cập đến những cân nhắc quốc tế hóa. Một số trong số này là rất khó khăn để chiếm bởi vì họ đòi hỏi kiến thức không chỉ của những gì ngôn ngữ đang được sử dụng, nhưng cũng có thể đòi hỏi kiến thức cụ thể của các từ trong ngôn ngữ. Ví dụ, chữ "mb" của Ailen viết hoa là "mB" khi bắt đầu một từ. Một ví dụ khác, eszett của Đức, không bao giờ bắt đầu một từ (afaik), nhưng vẫn giúp minh họa vấn đề. Chữ eszett chữ thường (chữ nhật ký) viết hoa thành chữ viết hoa, chữ viết tắt chữ viết hoa có thể viết hoa thành chữ viết hoa hoặc chữ viết tắt - bạn có thể hiểu biết về ngôn ngữ tiếng Đức để biết điều đó là chính xác!
Ví dụ nổi tiếng nhất về các loại vấn đề này, có lẽ, là tiếng Thổ Nhĩ Kỳ. Trong tiếng Latin tiếng Thổ Nhĩ Kỳ, dạng viết hoa của chữ i là chữ I, trong khi dạng chữ thường của chữ I là - chúng là hai chữ cái khác nhau. May mắn thay, chúng tôi có một cách để giải thích cho điều này:
function capitalizeFirstLetter([ first, ...rest ], locale) {
return [ first.toLocaleUpperCase(locale), ...rest ].join('');
}
capitalizeFirstLetter("italy", "en") // "Italy"
capitalizeFirstLetter("italya", "tr") // "İtalya"
Trong trình duyệt, thẻ ngôn ngữ ưa thích nhất của người dùng được chỉ định bởi navigator.language
, một danh sách theo thứ tự ưu tiên được tìm thấy tại navigator.languages
và ngôn ngữ của một thành phần DOM nhất định có thể được lấy (thường) bằng các Object(element.closest('[lang]')).lang || YOUR_DEFAULT_HERE
tài liệu đa ngôn ngữ.
Trong các tác nhân hỗ trợ các lớp ký tự thuộc tính Unicode trong RegExp, được giới thiệu trong ES2018, chúng ta có thể dọn dẹp thêm mọi thứ bằng cách thể hiện trực tiếp những ký tự mà chúng ta quan tâm:
function capitalizeFirstLetter(str, locale=navigator.language) {
return str.replace(/^\p{CWU}/u, char => char.toLocaleUpperCase(locale));
}
Điều này có thể được điều chỉnh một chút để xử lý viết hoa nhiều từ trong một chuỗi với độ chính xác khá tốt. Thuộc tính CWU
hoặc thay đổi_When_Uppercued phù hợp với tất cả các điểm mã, tốt, thay đổi khi cấp trên. Chúng ta có thể thử điều này với một nhân vật digraph titlecased như người Hà Lan ij ví dụ:
capitalizeFirstLetter('ijsselmeer'); // "IJsselmeer"
Tại thời điểm viết bài (tháng 2 năm 2020), Firefox / Spidermonkey vẫn chưa triển khai bất kỳ tính năng RegExp nào được giới thiệu trong hai năm qua *****. Bạn có thể kiểm tra trạng thái hiện tại của tính năng này tại bảng compat Kangax . Babel có thể biên dịch các chữ RegExp với các tham chiếu thuộc tính đến các mẫu tương đương mà không có chúng, nhưng lưu ý rằng mã kết quả có thể rất lớn.
Trong tất cả khả năng, những người hỏi câu hỏi này sẽ không quan tâm đến việc viết hoa hoặc quốc tế hóa Deseret. Nhưng thật tốt khi nhận thức được những vấn đề này bởi vì rất có thể bạn sẽ gặp phải chúng ngay cả khi chúng không phải là mối quan tâm hiện tại. Chúng không phải là các trường hợp cạnh edge, hay đúng hơn, chúng không phải là các trường hợp cạnh theo định nghĩa - có cả một quốc gia nơi hầu hết mọi người nói tiếng Thổ Nhĩ Kỳ, và các đơn vị mã kết hợp với mật mã là một nguồn lỗi khá phổ biến (đặc biệt là với liên quan đến biểu tượng cảm xúc). Cả chuỗi và ngôn ngữ đều khá phức tạp!
* Đơn vị mã của UTF-16 / UCS2 cũng là các điểm mã Unicode theo nghĩa, ví dụ, U + D800 về mặt kỹ thuật là một điểm mã, nhưng đó không phải là ý nghĩa của nó ở đây ... mặc dù ... nó khá hay mờ. Tuy nhiên, những gì người thay thế chắc chắn không phải là USVs (giá trị vô hướng Unicode).
** Mặc dù nếu một đơn vị mã thay thế là mồ côi mồ côi - tức là không phải là một phần của cặp logic - bạn vẫn có thể nhận được người thay thế ở đây.
*** có lẽ. Tôi đã không kiểm tra nó. Trừ khi bạn xác định viết hoa là một nút cổ chai có ý nghĩa, tôi có thể sẽ không đổ mồ hôi - hãy chọn bất cứ điều gì bạn tin là rõ ràng và dễ đọc nhất.
**** một chức năng như vậy có thể muốn kiểm tra cả đơn vị mã thứ nhất và thứ hai thay vì chỉ đơn vị thứ nhất, vì có thể đơn vị đầu tiên là một đại diện thay thế mồ côi. Ví dụ: đầu vào "\ uD800x" sẽ viết hoa chữ X như vốn có, có thể hoặc không được mong đợi.
***** Đây là vấn đề Bugzilla nếu bạn muốn theo dõi tiến trình trực tiếp hơn.