Tùy chỉnh localized trong Swift.
Bài đăng này đã không được cập nhật trong 3 năm
-
Việc hỗ trợ nhiều ngôn ngữ là một yếu tố cần thiết để App của bạn trở nên phổ biến trên App Store vì chung quy lại thì người sử dụng nào cũng muốn sử dụng ngôn ngữ mẹ đẻ của mình.
-
Chính vì tâm lý này mà Apple đã cung cấp cho chúng ta kha khá
API
để xử lý cácresource
nhưlocalized
với các tuỳ chỉnh cần thiết để chúng ta có thể hiển thị đa ngôn ngữ cho App. -
Việc
render
UI
cho các quốc gia khác nhau trở nên đơn giản với việc định nghĩa sẵn củadeveloper
.
// English
"NewMovies" = "**New** movies";
// Swedish
"NewMovies" = "**Nya** filmer";
// Polish
"NewMovies" = "**Nowe** filmy";
// VietNam
"NewMovies" = "**Phim Mới**";
- Chúng ta sẽ thực hiện chia nhỏ
format
củastring
trên thành từng đoạntext
nhỏ bằng cách sử dụngNSAtributedString
. Chúng ta cần định nghĩatype
choLocalizedString
để có thểimplement
tất cả cáclogic
được yêu cầu, tiếp đó chúng ta cần khởi tạo mộtinstance
với cáclocalized string key
để có thể xử lýraw
String
bằng việc sử dụngmethod
NSLocalizedString
:
struct LocalizedString {
var key: String
init(_ key: String) {
self.key = key
}
func resolve() -> String {
NSLocalizedString(key, comment: "")
}
}
extension LocalizedString: ExpressibleByStringLiteral {
init(stringLiteral value: StringLiteralType) {
key = value
}
}
- Với định dạng
type
bên trên thì chúng ta bây giờ hoàn toàn có thể sử dụngNSAtributedString
để chuyển đổi và hiển thịUI
.
1/ Attributed strings:
-
Như tên định nghĩa thì
NSAtributedString
cho phép chúng ta thêm vào các thuộc tính để hiển thị cho chuỗiString
mặc định trong trường hợp chúng ta cần có các chú thích, nhấn mạnh cho chuỗistring
đó. -
Chúng ta cần mở rộng
LocalizedString
để có thể thực hiện điều đó bằng việc thêm vàomethod
để có thể chia nhỏ chuỗiraw localizlized
thành cáccomponent
nhỏ với việc sử dụngMarkdown-style
**
sau đó thêm vàostyle
tuỳ chỉnh chocomponent
.
extension LocalizedString {
typealias Fonts = (default: UIFont, bold: UIFont)
static func defaultFonts() -> Fonts {
let font = UIFont.preferredFont(forTextStyle: .body)
return (font, .boldSystemFont(ofSize: font.pointSize))
}
func attributedString(
withFonts fonts: Fonts = defaultFonts()
) -> NSAttributedString {
let components = resolve().components(separatedBy: "**")
let sequence = components.enumerated()
let attributedString = NSMutableAttributedString()
return sequence.reduce(into: attributedString) { string, pair in
let isBold = !pair.offset.isMultiple(of: 2)
let font = isBold ? fonts.bold : fonts.default
string.append(NSAttributedString(
string: pair.element,
attributes: [.font: font]
))
}
}
}
-
Sử dụng tuỳ chỉnh trên chúng ta có thể
render
các chuỗilocalized
với cácstyle
đã được tuỳ chỉnh cho cácUIKit
class
nhưUILabel
hayUITextView
. -
Để sử dụng tuỳ chỉnh trên cho
SwiftUI
chúng ta cầnrefactor
lại đoạncode
trên để có thể tái sử dụng cácrender logic
tránh việc phảicopy
lại. -
Phương án
refactor
ở đây là sử dụnggeneric
để giảm thiểu việc phải viết đi viết lại các đoạncode
đểrender
UI
. Cụ thể ở đây chúng ta dùngmethod
reduce
:
private extension LocalizedString {
func render<T>(
into initialResult: T,
handler: (inout T, String, _ isBold: Bool) -> Void
) -> T {
let components = resolve().components(separatedBy: "**")
let sequence = components.enumerated()
return sequence.reduce(into: initialResult) { result, pair in
let isBold = !pair.offset.isMultiple(of: 2)
handler(&result, pair.element, isBold)
}
}
}
- So với
method
NSAtributedString
cơ bản trước đó chúng ta cần tập trung cho việc chú thích thêm cũng như kết hợp các chuỗistring
đã được truyền vàohandler
:
extension LocalizedString {
...
func attributedString(
withFonts fonts: Fonts = defaultFonts()
) -> NSAttributedString {
render(
into: NSMutableAttributedString(),
handler: { fullString, string, isBold in
let font = isBold ? fonts.bold : fonts.default
fullString.append(NSAttributedString(
string: string,
attributes: [.font: font]
))
}
)
}
}
2/ Cách sử dụng localized để sử dụng trong SwiftUI Text:
- Một trong những tính năng không được chú ý của
Text
trongSwiftUI
là chúng ta có sử dụngoperation
add
để kết hợp nhiều chuỗistring
với các tuỳ chỉnh khác nhau lại. Chúng ta cần mở rộngAPI
LocalizedString
để gọi mộtmethod
với với tênrender
để kết hợp từng chuỗistring
cần tuỳ chỉnh lại:
extension LocalizedString {
func styledText() -> Text {
render(into: Text("")) { fullText, string, isBold in
var text = Text(string)
if isBold {
text = text.bold()
}
fullText = fullText + text
}
}
}
3/ Tùy chỉnh localized
để có thể sử dụng cho cả UIKit và SwiftUI:
- Chúng ta cùng xem xét một số tùy chỉnh cho
UILabel
vàText
khiến chúng trở nên dễ sử dụng hơn, cho phép chúng ta có thể khởi tạo trực tiếp chúng với các giá trị cũng nhưstyle
mà ta mong muốn:
extension UILabel {
convenience init(styledLocalizedString string: LocalizedString) {
self.init(frame: .zero)
attributedText = string.attributedString()
}
}
extension Text {
init(styledLocalizedString string: LocalizedString) {
self = string.styledText()
}
}
- Cách sử dụng
UILabel
choUIKit
cũng nhưText
choSwiftUI
kèm theostring
đã đượclocalized
trở nên vô cùng ngắn gọn và đơn giản như sau:
// UIKit
UILabel(styledLocalizedString: "NewMovies")
// SwiftUI
Text(styledLocalizedString: "NewMovies")
-
Cách sử dụng này kèm theo một vấn đề mà chúng ta cần lưu tâm đó là chúng ta luôn
parsing
lại mỗistring
khi chúng cácLabel
cũng nhưText
này được gọi tới, đồng nghĩa nếu chúng ta luônupdate
lạiUI
thì chúng ta cần phảicaching
chúng lại tránh việcparsing
không cần thiết. -
Tất cả
string
mà chúng taparsing
đượcload
từstatic resource
, chúng ta nêncache
chúng lại một cách cẩn thận và để thực hiện điều đó chúng ta sử dụngtype
Cache
để tùy chỉnhmethod
render
có thểread
vàwrite
từtype
Cache
:
private extension LocalizedString {
static let attributedStringCache = Cache<String, NSMutableAttributedString>()
static let swiftUITextCache = Cache<String, Text>()
func render<T>(
into initialResult: @autoclosure () -> T,
cache: Cache<String, T>,
handler: (inout T, String, _ isBold: Bool) -> Void
) -> T {
if let cached = cache.value(forKey: key) {
return cached
}
let components = resolve().components(separatedBy: "**")
let sequence = components.enumerated()
let result = sequence.reduce(into: initialResult()) { result, pair in
let isBold = !pair.offset.isMultiple(of: 2)
handler(&result, pair.element, isBold)
}
cache.insert(result, forKey: key)
return result
}
}
- Hãy chú ý rằng
attributedStringCache
lưu giữNSMutableAttributedString
instance
đó là bởi vì chúng ta làm việc vớitype
chúng ta gọirender
từmethod``attributedString
. Không có tác dụng phụ gì khi chúng ta sử dụng nhữngmutable
instance
với cáctype``LocalizedString
nhưng từ bây giờ chúng ta nêncopy
tất cả cácattributed
string
trước khi trả giá trị cho chúng để có thể tránh các tai nạn có thể xảy ra khi chia sẻ cácmutable
state
.
extension LocalizedString {
...
func attributedString(
withFonts fonts: Fonts = defaultFonts()
) -> NSAttributedString {
let string = render(
into: NSMutableAttributedString(),
cache: Self.attributedStringCache,
handler: { fullString, string, isBold in
...
}
)
return NSAttributedString(attributedString: string)
}
func styledText() -> Text {
render(
into: Text(""),
cache: Self.swiftUITextCache,
handler: { fullText, string, isBold in
...
}
)
}
}
All rights reserved