Tùy chỉnh localized trong Swift.
Bài đăng này đã không được cập nhật trong 4 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ácresourcenhưlocalizedvớ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 renderUIcho 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ỏ formatcủastringtrên thành từng đoạntextnhỏ bằng cách sử dụngNSAtributedString. Chúng ta cần định nghĩatypechoLocalizedStringđể có thểimplementtất cả cáclogicđược yêu cầu, tiếp đó chúng ta cần khởi tạo mộtinstancevới cáclocalized string keyđể có thể xử lýrawStringbằng việc sử dụngmethodNSLocalizedString:
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 typebê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ì NSAtributedStringcho phép chúng ta thêm vào các thuộc tính để hiển thị cho chuỗiStringmặ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 localizlizedthành cáccomponentnhỏ với việc sử dụngMarkdown-style**sau đó thêm vàostyletuỳ 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ể rendercác chuỗilocalizedvới cácstyleđã được tuỳ chỉnh cho cácUIKitclassnhưUILabelhayUITextView.
- 
Để sử dụng tuỳ chỉnh trên cho SwiftUIchúng ta cầnrefactorlại đoạncodetrên để có thể tái sử dụng cácrender logictránh việc phảicopylạ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đểrenderUI. Cụ thể ở đây chúng ta dùngmethodreduce:
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 methodNSAtributedStringcơ 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 TexttrongSwiftUIlà chúng ta có sử dụngoperationaddđể kết hợp nhiều chuỗistringvới các tuỳ chỉnh khác nhau lại. Chúng ta cần mở rộngAPILocalizedStringđể gọi mộtmethodvới với tênrenderđể kết hợp từng chuỗistringcầ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 UILabelvàTextkhiế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ưstylemà 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 UILabelchoUIKitcũng nhưTextchoSwiftUIkèm theostringđã đượclocalizedtrở 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 parsinglại mỗistringkhi chúng cácLabelcũng nhưTextnày được gọi tới, đồng nghĩa nếu chúng ta luônupdatelạiUIthì chúng ta cần phảicachingchúng lại tránh việcparsingkhông cần thiết.
- 
Tất cả stringmà chúng taparsingđượcloadtừstatic resource, chúng ta nêncachechúng lại một cách cẩn thận và để thực hiện điều đó chúng ta sử dụngtypeCacheđể tùy chỉnhmethodrendercó thểreadvàwritetừtypeCache:
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 attributedStringCachelưu giữNSMutableAttributedStringinstanceđó là bởi vì chúng ta làm việc vớitypechúng ta gọirendertừmethod``attributedString. Không có tác dụng phụ gì khi chúng ta sử dụng nhữngmutableinstancevới cáctype``LocalizedStringnhưng từ bây giờ chúng ta nêncopytất cả cácattributedstringtrướ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ácmutablestate.
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
 
  
 