Exploring Foundation Models: Supported Languages and Internationalization
44% OFF - Black Friday Sale
Use the coupon "BLACKFRIDAY" at checkout page for AI Driven Coding, Foundation Models MLX, MusicKit, freelancing or technical writing.
Apple Intelligence supports 23 locales (not languages), and Foundation Models supports all of them. This post shows how to inspect supported locales at runtime and fetch responses in the user's language for regional differences.
If your app is localized, your AI should be as well. When a user runs your app in Korean, they expect AI responses in Korean. Since Apple Intelligence supports Korean (and other locales), you can use the on-device model's multilingual capabilities so the experience feels consistent and native.
To build this experience, you first need to understand which languages are available on the current device and how to present them to your users or automatically detect the user's language and respond in that language.
Querying Available Languages
On the system model class, there exists a property supportedLanguages that you can query to get the supported locales. As Apple Intelligence adds more languages later next year, always check at runtime instead of hardcoding the list.
import FoundationModels
let model = SystemLanguageModel.default
let supported = model.supportedLanguages // [Locale.Language]Creating Language Selection
Once you have the supported languages, the next step is presenting them to users in a way they can understand. Raw Locale.Language objects are not user friendly. You need to convert them to readable text for your audience.
for language in supported {
let lang = language.languageCode?.identifier ?? "unknown"
let region = language.region?.identifier ?? "—"
print("- \(lang) (\(region))")
}This prints the language and region codes:
- fr (FR)
- ko (KR)
- en (GB)
- de (DE)
- zh (CN)Use Locale.current to get the current locale. The localizedString(forLanguageCode:) and localizedString(forRegionCode:) methods return the localized names for the language and region.
for language in supported {
let code = language.languageCode?.identifier ?? ""
let region = language.region?.identifier ?? ""
let name = Locale.current.localizedString(forLanguageCode: code) ?? code
let regionName = region.isEmpty ? nil : (Locale.current.localizedString(forRegionCode: region) ?? region)
print(regionName != nil ? "- \(name) (\(regionName!))" : "- \(name)")
}This prints the language and region names:
- French (France)
- Spanish (Spain)
- Spanish (Latin America)
- English (United States)
- Portuguese (Brazil)
- English (United Kingdom)
- Chinese (China mainland)Now that you understand how to query and display available languages, here is the complete set of supported locales and their characteristics.
Language Support Matrix
As of the latest iOS 26.1 release, the given languages are supported by the system language model, with different locale variants. The set can change across releases; so make sure to check at runtime.
| Language | Regions | Notes |
|---|---|---|
| Dutch | NL | Dutch (Netherlands) |
| Swedish | SE | Swedish (Sweden) |
| Turkish | TR | Turkish (Türkiye) |
| Spanish | 419 | Spanish (Latin America) |
| Spanish | US | Spanish (United States) |
| Spanish | ES | Spanish (Spain) |
| Danish | DK | Danish (Denmark) |
| Chinese | TW | Chinese (Taiwan) |
| Chinese | HK | Chinese (Hong Kong) |
| Chinese | CN | Chinese (China mainland) |
| Italian | IT | Italian (Italy) |
| Japanese | JP | Japanese (Japan) |
| Norwegian | NO | Norwegian Bokmål (Norway) |
| French | CA | French (Canada) |
| French | FR | French (France) |
| Portuguese | BR | Portuguese (Brazil) |
| Portuguese | PT | Portuguese (Portugal) |
| English | US | English (United States) |
| English | AU | English (Australia) |
| English | GB | English (United Kingdom) |
| German | DE | German (Germany) |
| Korean | KR | Korean (South Korea) |
| Vietnamese | VN | Vietnamese (Vietnam) |
Prefer localized names over codes in user interfaces and respect regional formatting for dates, numbers, currency, and units. A good user experience includes persisting the user's choice and providing a clear way to change it. Fall back gracefully when the preferred locale is unavailable, defaulting to English. According to Apple's research paper on the foundation models, quality varies by language and is generally worse for non-English languages. Evaluate your features with your target locales.
Generating Responses in Multiple Languages
With the foundation of language detection and display in place, you can now focus on the core functionality: generating responses in the user's preferred language. If your feature needs to respond in different languages, you prompt in that language and the model responds accordingly.
The snippet below reuses a single session and iterates through a set of localized prompts. It prints the language label and the response line so you can see how output looks per locale. This is useful when you want to sanity-check phrasing and tone, or when you need to capture short, deterministic answers (for example, labels, confirmations, or brief facts). You can replace the sample prompts with your app's real sentences to preview how the model will phrase them.
import FoundationModels
struct LanguagePrompt {
let name: String
let text: String
}
let session = LanguageModelSession(model: SystemLanguageModel.default)
let prompts: [LanguagePrompt] = [
.init(name: "English", text: "What is the capital of France? Please provide a brief answer."),
.init(name: "Spanish", text: "¿Cuál es la capital de España? Por favor, proporciona una respuesta breve."),
.init(name: "French", text: "Quelle est la capitale de l'Allemagne ? Veuillez donner une réponse brève."),
.init(name: "German", text: "Was ist die Hauptstadt von Italien? Bitte geben Sie eine kurze Antwort."),
.init(name: "Italian", text: "Qual è la capitale del Portogallo? Per favore, fornisci una risposta breve."),
.init(name: "Portuguese", text: "Qual é a capital do Brasil? Por favor, forneça uma resposta breve."),
.init(name: "Chinese", text: "中国的首都是什么?请简要回答。"),
.init(name: "Japanese", text: "日本の首都は何ですか?簡潔にお答えください。"),
.init(name: "Korean", text: "한국의 수도는 어디인가요? 간단히 답해주세요.")
]
for prompt in prompts {
do {
let response = try await session.respond(to: prompt.text)
print("\(prompt.name): \(response.content)")
} catch {
print("\(prompt.name): Error -> \(error.localizedDescription)")
}
}Running the example produces language-appropriate answers:
English: The capital of France is Paris.
Spanish: La capital de España es Madrid.
French: La capitale de l'Allemagne est Berlin.
German: Die Hauptstadt Italiens ist Rom.
Italian: La capitale del Portogallo è Lisbona.
Portuguese: A capital do Brasil é Brasília.
Chinese: 中国的首都是北京。
Japanese: 日本の首都は東京です。
Korean: 한국의 수도는 서울입니다.You can apply the same pattern to your actual sentences and system instructions so that your app generates responses in the user's language. Keep prompts natural to the target language and avoid mixed-language sentences unless you specifically want bilingual output. If you need a consistent tone or style, add brief instructions when creating the session (for example, "Use concise, neutral language").
Handling Multilingual Scenarios
Beyond basic language-specific responses, apps that support multiple languages face additional challenges. One of the most important considerations when implementing multilingual features is understanding how session management affects language handling. The behavior differs significantly between fresh sessions and persistent sessions when dealing with mixed-language input.
The Fresh Session Problem
When you create a new session for each multilingual interaction, Foundation Models can exhibit problematic behaviors:
-
Safety Trigger False Positives: Words that are innocent in one language may trigger safety mechanisms when misinterpreted. For example, one weird examples I faced was the French word "pain" (meaning bread) triggered safety warning when processed in a fresh session without proper context.
-
Generic Template Responses: Mixed-language input in fresh sessions often results in generic, unhelpful responses that default to suggesting users check Apple's website rather than responding with the content.
-
Context Loss: Without conversational context, the model struggles to understand the intent behind code-switching (natural mixing of languages within a conversation).
The Persistent Session Solution
Using a single session for multilingual conversations has much better results:
import FoundationModels
// Create a single session for the multilingual conversation
let session = LanguageModelSession(model: SystemLanguageModel.default)
do {
// English interaction
let english = try await session.respond(to: "Hello, how are you?")
print("English: \(english.content)")
// Switch to Spanish in the same session
let spanish = try await session.respond(to: "Hola, ¿cómo estás?")
print("Spanish: \(spanish.content)")
// Ask to switch back to English
let switchBack = try await session.respond(to: "Now answer in English please")
print("Switch request: \(switchBack.content)")
// Test context retention
let memory = try await session.respond(to: "What language did I first speak to you in?")
print("Memory test: \(memory.content)")
} catch {
print("Error: \(error)")
}Here is the output:
English: Hello! I am just a program, so I do not have feelings, but I am here and ready to help you. How can I assist you today?
Spanish: Hola, estoy bien, gracias. ¿En qué puedo ayudarte hoy?
Switch request: Of course! I am doing well, thank you. How can I assist you today?
Memory test: You initially spoke to me in Spanish.This approach produces natural response and maintains conversational memory across language switches.
Code-Switching and Mixed-Language Input
When users naturally mix languages (code-switching), session strategy becomes even more important:
let mixedLanguagePrompts = [
"Hello, mi nombre es Juan. How are you today?",
"I went to the marché yesterday to buy some pain.", // French: market, bread
"Das ist very interesting, nicht wahr?", // German-English mix
"Estoy muy tired después de working todo el día." // Spanish-English mix
]Best Practices
-
Use Persistent Sessions: Create one session per conversation or user interaction, not per message.
-
Provide Language Context: When possible, establish the primary language early in the conversation:
let session = LanguageModelSession(
model: SystemLanguageModel.default,
instructions: "Please respond primarily in Spanish, but understand mixed Spanish-English input."
)
let english = try await session.respond(to: "Hello, how are you?")-
Handle Code-Switching Gracefully: Design your app to expect and handle natural language mixing, especially in multilingual communities.
-
Test Mixed-Language Scenarios: Always test your multilingual features with realistic mixed-language input that your users might actually provide.
-
Understand Regional Differences: The same language can behave differently across regions (es-ES vs es-419 vs es-US), and session management helps maintain consistency.
Zenther: Language Detection and Localization
My Zenther fitness app has a language-aware AI implementation for nutrition tracking. This example shows automatic language detection, forced response language, and debugging patterns for multilingual AI features.
Automatic Language Detection with Fallback
The nutrition analysis service automatically detects the user's language and configures AI responses accordingly:
// From MacroPlanner.swift:31-55, 57-117
struct NutritionAnalysisService {
private let nutritionSession: LanguageModelSession
private let userLanguage: String
// PFIGSCJK language mapping (Portuguese, French, Italian, German, Spanish, Chinese Simplified, Japanese, Korean)
private static let languageMapping: [String: String] = [
"pt": "Portuguese",
"fr": "French",
"it": "Italian",
"de": "German",
"es": "Spanish",
"zh": "Chinese (Simplified)",
"ja": "Japanese",
"ko": "Korean",
"en": "English"
]
init() {
// Get user's current locale and language
let userLocale = Locale.autoupdatingCurrent
let languageCode = userLocale.language.languageCode?.identifier ?? "en"
let localeIdentifier = userLocale.identifier
// Print locale information for debugging
print("User Locale: \(localeIdentifier)")
print("Language Code: \(languageCode)")
self.userLanguage = Self.languageMapping[languageCode] ?? "English"
print("Responding in: \(userLanguage)")
self.nutritionSession = LanguageModelSession(instructions: """
You are a nutrition expert specializing in food analysis and macro tracking.
IMPORTANT: Respond in \(userLanguage). All your responses must be in the user's language: \(userLanguage)
When parsing food descriptions:
- Estimate realistic portions for typical adults
- Consider cooking methods (grilled vs fried affects calories)
- Account for common additions (butter, oil, condiments)
- Be practical with portion sizes people actually eat
- Round to reasonable numbers (do not say 247.3 calories, say ~250)
For nutritional guidance:
- Focus on energy for fitness and performance
- Be encouraging and supportive like a fitness coach
- Highlight good nutritional choices
- Suggest balance when needed
- Keep responses brief and actionable
Tone: Supportive, knowledgeable, practical, encouraging.
Language: \(userLanguage)
""")
}
}Forced Response Language in Prompts
The app ensures consistent language responses by reinforcing language requirements in every prompt:
func parseFood(_ description: String) async throws -> ParsedFood {
let prompt = """
RESPOND IN \(userLanguage). Parse this food description into nutritional data: "\(description)"
Examples of good parsing:
"I had 2 scrambled eggs with toast" → Consider: 2 large eggs (~140 cal), 1 slice toast (~80 cal), cooking butter (~30 cal)
"protein shake after workout" → Consider: 1 scoop protein powder (~120 cal) + milk/water
"pizza slice for lunch" → Consider: 1 slice medium pizza (~280 cal)
"handful of almonds" → Consider: ~20 almonds (~160 cal)
Be realistic about portions people actually eat.
Account for cooking methods and common additions.
Language: \(userLanguage)
"""
let response = try await nutritionSession.respond(
to: prompt,
generating: NutritionParseResult.self
)
return ParsedFood(
foodName: response.content.foodName,
calories: response.content.calories,
proteinGrams: response.content.proteinGrams,
carbsGrams: response.content.carbsGrams,
fatGrams: response.content.fatGrams
)
}Language Mapping Strategy and Logging
The app includes detailed logging to debug language detection issues and proper AI responses:
- Automatic Detection: Uses
Locale.autoupdatingCurrentto detect user's system language - Explicit Mapping: Maps language codes to full language names for clearer AI instructions
- Fallback Strategy: Defaults to English for unsupported languages
- Debug Logging: Prints locale information for troubleshooting
- Double Enforcement: States language requirements in both session instructions and individual prompts
What's Next
While persistent sessions are generally better for multilingual interactions, use fresh sessions when you need to completely reset context between unrelated conversations, when previous context might interfere with current requests, or when building batch processing workflows where each item should be independent.
44% OFF - Black Friday Sale
Use the coupon "BLACKFRIDAY" at checkout page for AI Driven Coding, Foundation Models MLX, MusicKit, freelancing or technical writing.
Post Topics
Explore more in these categories: