使用 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)不是线程安全的。每个实例应仅在一个线程或调度队列中使用。
分词
使用 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:) 检测数字或表情符号标记。
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 |
每个标记的语言 |
.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() 显示内置翻译界面。
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: "zh-Hans"))
}
}
.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
}
}
}
}
}
批量翻译
在单个会话中翻译多个字符串。
.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
}
不要:为每个标记创建新的标注器
创建和配置标注器开销很大。对同一文本应重复使用。
// 错误:每个单词新建标注器
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实例在单个线程中使用 - [ ] 标注器为每段文本创建一次,而非每个标记
- [ ] 语言检测对短文本使用约束/提示
- [ ] 使用
NLEmbedding前检查可用性(不可用时返回 nil) - [ ] 尝试翻译前检查
LanguageAvailability - [ ]
.translationTask()在 SwiftUI 视图层次结构中使用 - [ ] 批量翻译使用
clientIdentifier匹配响应与请求 - [ ] 情感分数作为可选值处理(对不支持的语言可能返回 nil)
- [ ] 命名实体识别中使用
.joinNames选项以保持多词名称完整 - [ ] 自定义 ML 模型通过
NLModel加载,而非原始 Core ML






