This document provides detailed information about the internationalization system in the Summon library. The I18n system provides comprehensive support for multiple languages, right-to-left (RTL) layouts, and localized content.
The Summon I18n system provides:
data class Language(
val code: String, // ISO language code (e.g., "en", "ar", "he")
val name: String, // Display name (e.g., "English", "العربية")
val direction: LayoutDirection
)
enum class LayoutDirection {
LTR, // Left-to-Right (English, French, etc.)
RTL // Right-to-Left (Arabic, Hebrew, etc.)
}
val English = Language("en", "English", LayoutDirection.LTR)
val Arabic = Language("ar", "العربية", LayoutDirection.RTL)
val Hebrew = Language("he", "עברית", LayoutDirection.RTL)
val French = Language("fr", "Français", LayoutDirection.LTR)
Global configuration for supported languages and default settings.
object I18nConfig {
val supportedLanguages: List<Language>
var defaultLanguage: Language?
fun configure(block: I18nConfigBuilder.() -> Unit)
fun changeLanguage(languageCode: String): Boolean
}
class I18nConfigBuilder {
val languages: MutableList<Language>
var defaultLanguage: Language?
fun language(
code: String,
name: String,
direction: LayoutDirection = LayoutDirection.LTR
)
fun defaultLanguage(language: Language)
}
I18nConfig.configure {
language("en", "English", LayoutDirection.LTR)
language("ar", "العربية", LayoutDirection.RTL)
language("he", "עברית", LayoutDirection.RTL)
language("fr", "Français", LayoutDirection.LTR)
defaultLanguage = Language("en", "English", LayoutDirection.LTR)
}
The I18nContext provides translation functionality and language state to components.
data class I18nContext(
val language: String = "en",
val translations: Map<String, String> = emptyMap(),
val translator: (String, String) -> String = { key, lang -> key }
) {
fun getString(key: String): String
fun getString(key: String, vararg args: Any): String
}
val LocalI18n: CompositionLocal<I18nContext>
@Composable
fun I18nProvider(
language: String,
translations: Map<String, String> = emptyMap(),
translator: (String, String) -> String = { key, _ -> key },
content: @Composable () -> Unit
)
@Composable
fun rememberI18n(): I18nContext
// Provide I18n context
@Composable
fun App() {
val translations = mapOf(
"welcome" to "Welcome",
"hello_user" to "Hello, {0}!",
"button_submit" to "Submit"
)
I18nProvider(
language = "en",
translations = translations
) {
MainContent()
}
}
// Use translations in components
@Composable
fun WelcomeMessage(userName: String) {
val i18n = rememberI18n()
Text(i18n.getString("welcome"))
Text(i18n.getString("hello_user", userName))
}
@Composable
fun LanguageProvider(
language: Language,
content: @Composable () -> Unit
)
val LocalLanguage: CompositionLocal<Language>
val LocalLayoutDirection: CompositionLocal<() -> LayoutDirection>
@Composable
fun rememberCurrentLanguage(): Language
@Composable
fun rememberLayoutDirection(): LayoutDirection
// Global language switching
fun changeLanguage(languageCode: String): Boolean
// Component-level language switching
@Composable
fun LanguageSwitcher(
currentLanguage: String,
onLanguageChange: (String) -> Unit
) {
val supportedLanguages = I18nConfig.supportedLanguages
Select(
value = currentLanguage,
onValueChange = onLanguageChange,
options = supportedLanguages.map {
SelectOption(it.code, it.name)
}
)
}
RTL utilities for proper right-to-left layout support.
object RtlUtils {
@Composable
fun isRtl(): Boolean
@Composable
fun <T> directionalValue(ltrValue: T, rtlValue: T): T
}
@Composable
fun DirectionalComponent() {
val isRtl = RtlUtils.isRtl()
val textAlign = RtlUtils.directionalValue(
ltrValue = "left",
rtlValue = "right"
)
val marginDirection = RtlUtils.directionalValue(
ltrValue = "margin-left",
rtlValue = "margin-right"
)
Text(
text = "Content",
modifier = Modifier()
.style("text-align", textAlign)
.style(marginDirection, "16px")
)
}
object StringResources {
fun loadTranslations(language: String): Map<String, String>
fun getTranslation(key: String, language: String): String?
}
class TranslationProvider {
fun getTranslations(language: String): Map<String, String>
fun addTranslations(language: String, translations: Map<String, String>)
}
// Define translations
val englishTranslations = mapOf(
"app_name" to "My App",
"welcome_message" to "Welcome to our application!",
"user_greeting" to "Hello, {0}!",
"item_count" to "You have {0} items",
"button_save" to "Save",
"button_cancel" to "Cancel",
"error_network" to "Network connection error"
)
val arabicTranslations = mapOf(
"app_name" to "تطبيقي",
"welcome_message" to "مرحباً بك في تطبيقنا!",
"user_greeting" to "مرحباً، {0}!",
"item_count" to "لديك {0} عناصر",
"button_save" to "حفظ",
"button_cancel" to "إلغاء",
"error_network" to "خطأ في الاتصال بالشبكة"
)
// Usage with placeholder replacement
@Composable
fun UserProfile(user: User) {
val i18n = rememberI18n()
Text(i18n.getString("user_greeting", user.name))
Text(i18n.getString("item_count", user.itemCount))
}
Layout-aware modifiers that adapt to text direction.
@Composable
fun Modifier.directionalPadding(
start: String,
top: String,
end: String,
bottom: String
): Modifier
@Composable
fun Modifier.directionalMargin(
start: String,
top: String,
end: String,
bottom: String
): Modifier
@Composable
fun Modifier.mirrorInRtl(): Modifier
@Composable
fun Modifier.directionalRow(): Modifier
@Composable
fun Card(content: @Composable () -> Unit) {
Box(
modifier = Modifier()
.backgroundColor("#ffffff")
.borderRadius("8px")
.directionalPadding(
start = "16px",
top = "12px",
end = "16px",
bottom = "12px"
)
.directionalMargin(
start = "8px",
top = "0px",
end = "8px",
bottom = "16px"
)
) {
content()
}
}
@Composable
fun IconButton(icon: String, onClick: () -> Unit) {
Button(
onClick = onClick,
modifier = Modifier()
.mirrorInRtl() // Mirror the icon in RTL
) {
Icon(icon)
}
}
@Composable
fun NavigationRow() {
Row(
modifier = Modifier()
.directionalRow() // Reverses to row-reverse in RTL
) {
Text("Previous")
Spacer(Modifier().weight(1f))
Text("Next")
}
}
@Composable
fun MultilingualApp() {
var currentLanguage by remember { mutableStateOf("en") }
// Configure supported languages
LaunchedEffect(Unit) {
I18nConfig.configure {
language("en", "English", LayoutDirection.LTR)
language("ar", "العربية", LayoutDirection.RTL)
language("he", "עברית", LayoutDirection.RTL)
defaultLanguage = Language("en", "English", LayoutDirection.LTR)
}
}
// Load translations for current language
val translations = remember(currentLanguage) {
when (currentLanguage) {
"en" -> englishTranslations
"ar" -> arabicTranslations
"he" -> hebrewTranslations
else -> englishTranslations
}
}
I18nProvider(
language = currentLanguage,
translations = translations
) {
LanguageProvider(
language = Language(currentLanguage, "",
if (currentLanguage in listOf("ar", "he")) LayoutDirection.RTL
else LayoutDirection.LTR
)
) {
AppContent(
onLanguageChange = { currentLanguage = it }
)
}
}
}
@Composable
fun UserCard(user: User) {
val i18n = rememberI18n()
val isRtl = RtlUtils.isRtl()
Card(
modifier = Modifier()
.fillMaxWidth()
.directionalPadding("16px", "12px", "16px", "12px")
) {
Row(
modifier = Modifier()
.directionalRow()
.alignItems(AlignItems.Center)
) {
// Profile image
Image(
src = user.avatarUrl,
alt = i18n.getString("user_avatar"),
modifier = Modifier()
.width("48px")
.height("48px")
.borderRadius("50%")
.directionalMargin(
start = "0px",
top = "0px",
end = "12px",
bottom = "0px"
)
)
// User info
Column {
Text(
text = user.name,
modifier = Modifier()
.themeTextStyle("subtitle")
.style("text-align",
RtlUtils.directionalValue("left", "right")
)
)
Text(
text = i18n.getString("user_status", user.status),
modifier = Modifier()
.themeTextStyle("caption")
.style("text-align",
RtlUtils.directionalValue("left", "right")
)
)
}
Spacer(Modifier().weight(1f))
// Action button
Button(
onClick = { /* action */ },
label = i18n.getString("button_contact"),
modifier = Modifier()
.mirrorInRtl() // Mirror button in RTL if it has directional icon
)
}
}
}
@Composable
fun ContactForm() {
val i18n = rememberI18n()
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var message by remember { mutableStateOf("") }
Form(
modifier = Modifier()
.maxWidth("500px")
.directionalPadding("20px", "20px", "20px", "20px")
) {
FormField(
label = i18n.getString("form_name_label"),
required = true
) {
TextField(
value = name,
onValueChange = { name = it },
placeholder = i18n.getString("form_name_placeholder"),
modifier = Modifier()
.fillMaxWidth()
.style("text-align",
RtlUtils.directionalValue("left", "right")
)
)
}
FormField(
label = i18n.getString("form_email_label"),
required = true
) {
TextField(
value = email,
onValueChange = { email = it },
type = "email",
placeholder = i18n.getString("form_email_placeholder"),
modifier = Modifier()
.fillMaxWidth()
.style("text-align", "left") // Email always LTR
)
}
FormField(
label = i18n.getString("form_message_label")
) {
TextField(
value = message,
onValueChange = { message = it },
placeholder = i18n.getString("form_message_placeholder"),
modifier = Modifier()
.fillMaxWidth()
.height("120px")
.style("text-align",
RtlUtils.directionalValue("left", "right")
)
)
}
Row(
modifier = Modifier()
.directionalRow()
.justifyContent(JustifyContent.SpaceBetween)
.marginTop("20px")
) {
Button(
onClick = { /* cancel */ },
label = i18n.getString("button_cancel"),
variant = ButtonVariant.SECONDARY
)
Button(
onClick = { /* submit */ },
label = i18n.getString("button_submit"),
variant = ButtonVariant.PRIMARY
)
}
}
}
"welcome_message" over "text1"directionalPadding() over fixed paddingWhen adding I18n to existing components: