使用 Apple 的 NaturalLanguage 框架對自然語言文字進行斷詞、標記與分析,並透過 Translation 框架進行語言翻譯。適用於在 iOS/macOS/visionOS 應用程式中加入語言辨識、情感分析、命名實體辨識、詞性標記、文字嵌入或應用程式內翻譯。
NaturalLanguage + Translation
分析自然語言文字,進行斷詞、詞性標記、命名實體辨識、情感分析、語言辨識以及詞/句嵌入。使用 Translation 框架在不同語言之間翻譯文字。
此技能涵蓋兩個相關框架:NaturalLanguage(
NLTokenizer、NLTagger、NLEmbedding)用於裝置端文字分析,以及 Translation(TranslationSession、LanguageAvailability)用於語言翻譯。
範圍界定: 請在已取得文字後使用此技能。它負責斷詞、語言辨識、詞性/命名實體標記、情感分析、嵌入、自訂 NLModel 分類器/標記器以及應用程式內翻譯。OCR 請交給 vision-framework,語音轉文字請交給 speech-recognition,UI 字串與地區格式請交給 ios-localization,生成式摘要或 Apple Intelligence 工作流程請交給 apple-on-device-ai。
目錄
設定
匯入 NaturalLanguage 進行文字分析,匯入 Translation 進行語言翻譯。NaturalLanguage 不需要特殊權限或功能。Translation 的可用性有區分:系統翻譯介面需要 iOS 17.4+ / macOS 14.4+,而 TranslationSession、.translationTask()、LanguageAvailability 以及批次翻譯需要 iOS 18+ / macOS 15+。
直接使用 TranslationSession(installedSource:target:) 是無 UI 的選項,但僅限於來源與目標語言已安裝在裝置上時。
import NaturalLanguage
import Translation
NaturalLanguage 類別(NLTokenizer、NLTagger)非執行緒安全。每個實例請在單一執行緒或 dispatch queue 中使用。
斷詞
使用 NLTokenizer 將文字分割成詞、句子或段落。
import NaturalLanguage
func tokenizeWords(in text: String) -> [String] {
let tokenizer = NLTokenizer(unit: .word)
tokenizer.string = text
let range = text.startIndex..<text.endIndex
return tokenizer.tokens(for: range).map { String(text[$0]) }
}
斷詞單位
| 單位 | 說明 |
|---|---|
.word |
個別詞彙 |
.sentence |
句子 |
.paragraph |
段落 |
.document |
整份文件 |
列舉並取得屬性
使用 enumerateTokens(in:using:) 來偵測數字或表情符號等 token。
let tokenizer = NLTokenizer(unit: .word)
tokenizer.string = text
tokenizer.enumerateTokens(in: text.startIndex..<text.endIndex) { range, attributes in
if attributes.contains(.numeric) {
print("數字:\(text[range])")
}
return true // 繼續列舉
}
語言辨識
使用 NLLanguageRecognizer 偵測字串的主要語言。
func detectLanguage(for text: String) -> NLLanguage? {
NLLanguageRecognizer.dominantLanguage(for: text)
}
// 多個假設與信心分數
func languageHypotheses(for text: String, max: Int = 5) -> [NLLanguage: Double] {
let recognizer = NLLanguageRecognizer()
recognizer.processString(text)
return recognizer.languageHypotheses(withMaximum: max)
}
限制辨識器只考慮預期語言,可提升短文字的準確度。
let recognizer = NLLanguageRecognizer()
recognizer.languageConstraints = [.english, .french, .spanish]
recognizer.processString(text)
let detected = recognizer.dominantLanguage
詞性標記
使用 NLTagger 辨識名詞、動詞、形容詞及其他詞類。
func tagPartsOfSpeech(in text: String) -> [(String, NLTag)] {
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = text
var results: [(String, NLTag)] = []
let range = text.startIndex..<text.endIndex
let options: NLTagger.Options = [.omitPunctuation, .omitWhitespace]
tagger.enumerateTags(in: range, unit: .word, scheme: .lexicalClass, options: options) { tag, tokenRange in
if let tag {
results.append((String(text[tokenRange]), tag))
}
return true
}
return results
}
常見標記方案
| 方案 | 輸出 |
|---|---|
.lexicalClass |
詞性(名詞、動詞、形容詞) |
.nameType |
命名實體類型(人物、地點、組織) |
.nameTypeOrLexicalClass |
合併命名實體辨識與詞性標記 |
.lemma |
詞彙的原形 |
.language |
每個 token 的語言 |
.sentimentScore |
情感極性分數 |
命名實體辨識
擷取人物、地點與組織。
func extractEntities(from text: String) -> [(String, NLTag)] {
let tagger = NLTagger(tagSchemes: [.nameType])
tagger.string = text
var entities: [(String, NLTag)] = []
let options: NLTagger.Options = [.omitPunctuation, .omitWhitespace, .joinNames]
tagger.enumerateTags(
in: text.startIndex..<text.endIndex,
unit: .word,
scheme: .nameType,
options: options
) { tag, tokenRange in
if let tag, tag != .other {
entities.append((String(text[tokenRange]), tag))
}
return true
}
return entities
}
// NLTag 值:.personalName、.placeName、.organizationName
情感分析
為文字情感評分,範圍從 -1.0(負面)到 +1.0(正面)。
func sentimentScore(for text: String) -> Double? {
let tagger = NLTagger(tagSchemes: [.sentimentScore])
tagger.string = text
let (tag, _) = tagger.tag(
at: text.startIndex,
unit: .paragraph,
scheme: .sentimentScore
)
return tag.flatMap { Double($0.rawValue) }
}
文字嵌入
使用 NLEmbedding 衡量詞彙或句子之間的語義相似度。
func wordSimilarity(_ word1: String, _ word2: String) -> Double? {
guard let embedding = NLEmbedding.wordEmbedding(for: .english) else { return nil }
return embedding.distance(between: word1, and: word2, distanceType: .cosine)
}
func findSimilarWords(to word: String, count: Int = 5) -> [(String, Double)] {
guard let embedding = NLEmbedding.wordEmbedding(for: .english) else { return [] }
return embedding.neighbors(for: word, maximumCount: count, distanceType: .cosine)
}
句子嵌入可比較整個句子。
func sentenceSimilarity(_ s1: String, _ s2: String) -> Double? {
guard let embedding = NLEmbedding.sentenceEmbedding(for: .english) else { return nil }
return embedding.distance(between: s1, and: s2, distanceType: .cosine)
}
翻譯
系統翻譯覆蓋層
使用 .translationPresentation() 顯示內建翻譯 UI。
import SwiftUI
import Translation
struct TranslatableView: View {
@State private var showTranslation = false
let text = "Hello, how are you?"
var body: some View {
Button { showTranslation = true } label: {
Text(text)
}
.buttonStyle(.plain)
.translationPresentation(
isPresented: $showTranslation,
text: text
)
}
}
程式化翻譯
在視圖環境中使用 .translationTask() 進行程式化翻譯。
struct TranslatingView: View {
@State private var translatedText = ""
@State private var translationErrorMessage: String?
@State private var configuration: TranslationSession.Configuration?
var body: some View {
VStack {
Text(translatedText)
Button("翻譯") {
configuration = .init(source: Locale.Language(identifier: "en"),
target: Locale.Language(identifier: "es"))
}
}
.translationTask(configuration) { session in
do {
let response = try await session.translate("Hello, world!")
await MainActor.run {
translatedText = response.targetText
translationErrorMessage = nil
}
} catch {
let message = error.localizedDescription
await MainActor.run {
translationErrorMessage = message
}
}
}
}
}
批次翻譯
在單一 session 中翻譯多個字串。
.translationTask(configuration) { session in
do {
let requests = texts.enumerated().map { index, text in
TranslationSession.Request(sourceText: text,
clientIdentifier: "\(index)")
}
let responses = try await session.translations(from: requests)
for response in responses {
print("\(response.sourceText) -> \(response.targetText)")
}
} catch {
// 處理取消、不支援的語言或下載拒絕。
}
}
檢查語言可用性
let availability = LanguageAvailability()
let status = await availability.status(
from: Locale.Language(identifier: "en"),
to: Locale.Language(identifier: "ja")
)
switch status {
case .installed: break // 可離線翻譯
case .supported: break // 需要下載
case .unsupported: break // 語言配對不可用
}
常見錯誤
不要:跨執行緒共用 NLTagger/NLTokenizer
這些類別非執行緒安全,會產生錯誤結果或崩潰。
// 錯誤
let sharedTagger = NLTagger(tagSchemes: [.lexicalClass])
DispatchQueue.concurrentPerform(iterations: 10) { _ in
sharedTagger.string = someText // 資料競爭
}
// 正確
await withTaskGroup(of: Void.self) { group in
for _ in 0..<10 {
group.addTask {
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = someText
// 處理...
}
}
}
不要:混淆 NaturalLanguage 與 Core ML
NaturalLanguage 提供內建的語言分析。自訂訓練模型請使用 Core ML。兩者可透過 NLModel 互補。
// 錯誤:嘗試用原始 Core ML 做命名實體辨識
let coreMLModel = try MLModel(contentsOf: modelURL)
// 正確:使用 NLTagger 進行內建命名實體辨識
let tagger = NLTagger(tagSchemes: [.nameType])
// 或透過 NLModel 載入自訂 Core ML 模型
let nlModel = try NLModel(mlModel: coreMLModel)
tagger.setModels([nlModel], forTagScheme: .nameType)
不要:假設所有語言都有嵌入
並非所有語言在裝置上都有詞彙或句子嵌入可用。
// 錯誤:強制解包
let embedding = NLEmbedding.wordEmbedding(for: .japanese)!
// 正確:處理 nil
guard let embedding = NLEmbedding.wordEmbedding(for: .japanese) else {
// 此語言無嵌入可用
return
}
不要:為每個 token 建立新的標記器
建立與設定標記器成本較高。請對同一段文字重複使用。
// 錯誤:每個詞建立新標記器
for word in words {
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = word
}
// 正確:設定一次字串,然後列舉
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = fullText
tagger.enumerateTags(in: fullText.startIndex..<fullText.endIndex,
unit: .word, scheme: .lexicalClass, options: []) { tag, range in
return true
}
不要:忽略短文字的語言提示
對短字串(約少於 20 個字元)的語言偵測不可靠。請設定限制或提示以提高準確度。
// 錯誤:偵測單一詞彙的語言
let lang = NLLanguageRecognizer.dominantLanguage(for: "chat") // 法文還是英文?
// 正確:提供上下文
let recognizer = NLLanguageRecognizer()
recognizer.languageHints = [.english: 0.8, .french: 0.2]
recognizer.processString("chat")
審查清單
- [ ]
NLTokenizer與NLTagger實例在單一執行緒中使用 - [ ] 標記器對每段文字只建立一次,而非每個 token
- [ ] 語言偵測對短文字使用限制或提示
- [ ] 使用
NLEmbedding前檢查可用性(若不可用則回傳 nil) - [ ] 嘗試翻譯前先檢查
LanguageAvailability - [ ]
.translationTask()在 SwiftUI 視圖層級內使用 - [ ] 批次翻譯使用
clientIdentifier來配對回應與請求 - [ ] 情感分數視為可選值(不支援的語言可能回傳 nil)
- [ ] 命名實體辨識使用
.joinNames選項以保留多詞名稱 - [ ] 自訂 ML 模型透過
NLModel載入,而非原始 Core ML




