Agent skill
natural-language
Tokenize, tag, and analyze natural language text using Apple's NaturalLanguage framework and translate between languages with the Translation framework. Use when adding language identification, sentiment analysis, named entity recognition, part-of-speech tagging, text embeddings, or in-app translation to iOS/macOS/visionOS apps.
Install this agent skill to your Project
npx add-skill https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/natural-language
SKILL.md
NaturalLanguage + Translation
Analyze natural language text for tokenization, part-of-speech tagging, named entity recognition, sentiment analysis, language identification, and word/sentence embeddings. Translate text between languages with the Translation framework. Targets Swift 6.3 / iOS 26+.
This skill covers two related frameworks: NaturalLanguage (
NLTokenizer,NLTagger,NLEmbedding) for on-device text analysis, and Translation (TranslationSession,LanguageAvailability) for language translation.
Contents
- Setup
- Tokenization
- Language Identification
- Part-of-Speech Tagging
- Named Entity Recognition
- Sentiment Analysis
- Text Embeddings
- Translation
- Common Mistakes
- Review Checklist
- References
Setup
Import NaturalLanguage for text analysis and Translation for language
translation. No special entitlements or capabilities are required for
NaturalLanguage. Translation requires iOS 17.4+ / macOS 14.4+.
import NaturalLanguage
import Translation
NaturalLanguage classes (NLTokenizer, NLTagger) are not thread-safe.
Use each instance from one thread or dispatch queue at a time.
Tokenization
Segment text into words, sentences, or paragraphs with 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]) }
}
Token Units
| Unit | Description |
|---|---|
.word |
Individual words |
.sentence |
Sentences |
.paragraph |
Paragraphs |
.document |
Entire document |
Enumerating with Attributes
Use enumerateTokens(in:using:) to detect numeric or emoji tokens.
let tokenizer = NLTokenizer(unit: .word)
tokenizer.string = text
tokenizer.enumerateTokens(in: text.startIndex..<text.endIndex) { range, attributes in
if attributes.contains(.numeric) {
print("Number: \(text[range])")
}
return true // continue enumeration
}
Language Identification
Detect the dominant language of a string with NLLanguageRecognizer.
func detectLanguage(for text: String) -> NLLanguage? {
NLLanguageRecognizer.dominantLanguage(for: text)
}
// Multiple hypotheses with confidence scores
func languageHypotheses(for text: String, max: Int = 5) -> [NLLanguage: Double] {
let recognizer = NLLanguageRecognizer()
recognizer.processString(text)
return recognizer.languageHypotheses(withMaximum: max)
}
Constrain the recognizer to expected languages for better accuracy on short text.
let recognizer = NLLanguageRecognizer()
recognizer.languageConstraints = [.english, .french, .spanish]
recognizer.processString(text)
let detected = recognizer.dominantLanguage
Part-of-Speech Tagging
Identify nouns, verbs, adjectives, and other lexical classes with 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
}
Common Tag Schemes
| Scheme | Output |
|---|---|
.lexicalClass |
Part of speech (noun, verb, adjective) |
.nameType |
Named entity type (person, place, organization) |
.nameTypeOrLexicalClass |
Combined NER + POS |
.lemma |
Base form of a word |
.language |
Per-token language |
.sentimentScore |
Sentiment polarity score |
Named Entity Recognition
Extract people, places, and organizations.
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 values: .personalName, .placeName, .organizationName
Sentiment Analysis
Score text sentiment from -1.0 (negative) to +1.0 (positive).
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) }
}
Text Embeddings
Measure semantic similarity between words or sentences with 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)
}
Sentence embeddings compare entire sentences.
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)
}
Translation
System Translation Overlay
Show the built-in translation UI with .translationPresentation().
import SwiftUI
import Translation
struct TranslatableView: View {
@State private var showTranslation = false
let text = "Hello, how are you?"
var body: some View {
Text(text)
.onTapGesture { showTranslation = true }
.translationPresentation(
isPresented: $showTranslation,
text: text
)
}
}
Programmatic Translation
Use .translationTask() for programmatic translations within a view context.
struct TranslatingView: View {
@State private var translatedText = ""
@State private var configuration: TranslationSession.Configuration?
var body: some View {
VStack {
Text(translatedText)
Button("Translate") {
configuration = .init(source: Locale.Language(identifier: "en"),
target: Locale.Language(identifier: "es"))
}
}
.translationTask(configuration) { session in
let response = try await session.translate("Hello, world!")
translatedText = response.targetText
}
}
}
Batch Translation
Translate multiple strings in a single session.
.translationTask(configuration) { session in
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)")
}
}
Checking Language Availability
let availability = LanguageAvailability()
let status = await availability.status(
from: Locale.Language(identifier: "en"),
to: Locale.Language(identifier: "ja")
)
switch status {
case .installed: break // Ready to translate offline
case .supported: break // Needs download
case .unsupported: break // Language pair not available
}
Common Mistakes
DON'T: Share NLTagger/NLTokenizer across threads
These classes are not thread-safe and will produce incorrect results or crash.
// WRONG
let sharedTagger = NLTagger(tagSchemes: [.lexicalClass])
DispatchQueue.concurrentPerform(iterations: 10) { _ in
sharedTagger.string = someText // Data race
}
// CORRECT
await withTaskGroup(of: Void.self) { group in
for _ in 0..<10 {
group.addTask {
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = someText
// process...
}
}
}
DON'T: Confuse NaturalLanguage with Core ML
NaturalLanguage provides built-in linguistic analysis. Use Core ML for custom
trained models. They complement each other via NLModel.
// WRONG: Trying to do NER with raw Core ML
let coreMLModel = try MLModel(contentsOf: modelURL)
// CORRECT: Use NLTagger for built-in NER
let tagger = NLTagger(tagSchemes: [.nameType])
// Or load a custom Core ML model via NLModel
let nlModel = try NLModel(mlModel: coreMLModel)
tagger.setModels([nlModel], forTagScheme: .nameType)
DON'T: Assume embeddings exist for all languages
Not all languages have word or sentence embeddings available on device.
// WRONG: Force unwrap
let embedding = NLEmbedding.wordEmbedding(for: .japanese)!
// CORRECT: Handle nil
guard let embedding = NLEmbedding.wordEmbedding(for: .japanese) else {
// Embedding not available for this language
return
}
DON'T: Create a new tagger per token
Creating and configuring a tagger is expensive. Reuse it for the same text.
// WRONG: New tagger per word
for word in words {
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = word
}
// CORRECT: Set string once, enumerate
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
}
DON'T: Ignore language hints for short text
Language detection on short strings (under ~20 characters) is unreliable. Set constraints or hints to improve accuracy.
// WRONG: Detect language of a single word
let lang = NLLanguageRecognizer.dominantLanguage(for: "chat") // French or English?
// CORRECT: Provide context
let recognizer = NLLanguageRecognizer()
recognizer.languageHints = [.english: 0.8, .french: 0.2]
recognizer.processString("chat")
Review Checklist
-
NLTokenizerandNLTaggerinstances used from a single thread - Tagger created once per text, not per token
- Language detection uses constraints/hints for short text
-
NLEmbeddingavailability checked before use (returns nil if unavailable) - Translation
LanguageAvailabilitychecked before attempting translation -
.translationTask()used within a SwiftUI view hierarchy - Batch translation uses
clientIdentifierto match responses to requests - Sentiment scores handled as optional (may return nil for unsupported languages)
-
.joinNamesoption used with NER to keep multi-word names together - Custom ML models loaded via
NLModel, not raw Core ML
References
- Extended patterns (custom models, contextual embeddings, gazetteers): references/translation-patterns.md
- Natural Language framework
- NLTokenizer
- NLTagger
- NLEmbedding
- NLLanguageRecognizer
- Translation framework
- TranslationSession
- LanguageAvailability
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
weatherkit
Fetch current, hourly, and daily weather forecasts and display required attribution using WeatherKit. Use when integrating weather data, showing forecasts, handling weather alerts, displaying Apple Weather attribution, or querying historical weather statistics in iOS apps.
swiftui-patterns
Build SwiftUI views with modern MV architecture, state management, and view composition patterns. Covers @Observable ownership rules, @State/@Bindable/@Environment wiring, view decomposition, custom ViewModifiers, environment values, async data loading with .task, iOS 26+ APIs, Writing Tools, and performance guidelines. Use when structuring a SwiftUI app, managing state with @Observable, composing view hierarchies, or applying SwiftUI best practices.
homekit
Control smart-home accessories and commission Matter devices using HomeKit and MatterSupport. Use when managing homes/rooms/accessories, creating action sets or triggers, reading accessory characteristics, onboarding Matter devices, or building a third-party smart-home ecosystem app.
shareplay-activities
Build shared real-time experiences using GroupActivities and SharePlay. Use when implementing shared media playback, collaborative app features, synchronized game state, or any FaceTime/iMessage-integrated group activity on iOS, macOS, tvOS, or visionOS.
swiftui-gestures
Implement, review, or improve SwiftUI gesture handling. Use when adding tap, long press, drag, magnify, or rotate gestures, composing gestures with simultaneously/sequenced/exclusively, managing transient state with @GestureState, resolving parent/child gesture conflicts with highPriorityGesture or simultaneousGesture, building custom Gesture protocol conformances, or migrating from deprecated MagnificationGesture to MagnifyGesture or using the newer RotateGesture.
cryptotokenkit
Access security tokens and smart cards using CryptoTokenKit. Use when building token driver extensions with TKTokenDriver and TKToken, communicating with smart cards via TKSmartCard, implementing certificate-based authentication, managing token sessions, or integrating hardware security tokens with the system keychain.
Didn't find tool you were looking for?