Skip to content

Latest commit

 

History

History
631 lines (517 loc) · 15.4 KB

File metadata and controls

631 lines (517 loc) · 15.4 KB

Internationalization (I18n) API Reference

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.

Table of Contents


Overview

The Summon I18n system provides:

  • Multi-language support with dynamic language switching
  • RTL/LTR layout support for proper text direction handling
  • Translation management with placeholder support
  • Directional modifiers for layout-aware styling
  • Composable integration with CompositionLocal pattern

Key Features

  • Type-safe language definitions
  • Automatic layout direction detection
  • Directional padding and margin utilities
  • Translation key management
  • RTL-aware component mirroring

Core Types

Language Definition

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.)
}

Language Examples

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)

I18n Configuration

Global configuration for supported languages and default settings.

I18nConfig Object

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)
}

Configuration Example

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)
}

I18n Context

The I18nContext provides translation functionality and language state to components.

I18nContext Class

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
}

CompositionLocal Support

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

Usage Examples

// 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))
}

Language Management

Language Provider

@Composable
fun LanguageProvider(
    language: Language,
    content: @Composable () -> Unit
)

val LocalLanguage: CompositionLocal<Language>
val LocalLayoutDirection: CompositionLocal<() -> LayoutDirection>

@Composable
fun rememberCurrentLanguage(): Language

@Composable
fun rememberLayoutDirection(): LayoutDirection

Dynamic Language Switching

// 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 Support

RTL utilities for proper right-to-left layout support.

RtlUtils Object

object RtlUtils {
    @Composable
    fun isRtl(): Boolean

    @Composable
    fun <T> directionalValue(ltrValue: T, rtlValue: T): T
}

RTL Detection and Values

@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")
    )
}

Translation System

String Resources

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>)
}

Translation Management

// 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))
}

Directional Modifiers

Layout-aware modifiers that adapt to text direction.

Directional Padding and Margin

@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

RTL Mirroring

@Composable
fun Modifier.mirrorInRtl(): Modifier

@Composable
fun Modifier.directionalRow(): Modifier

Directional Modifier Examples

@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")
    }
}

Usage Examples

Complete I18n Setup

@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 }
            )
        }
    }
}

RTL-Aware Components

@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
            )
        }
    }
}

Form with I18n

@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
            )
        }
    }
}

Best Practices

  1. Use semantic translation keys: Prefer "welcome_message" over "text1"
  2. Plan for text expansion: Some languages require 30-40% more space
  3. Test with RTL languages: Ensure layouts work properly in both directions
  4. Use directional modifiers: Prefer directionalPadding() over fixed padding
  5. Consider cultural differences: Colors, icons, and layouts may have different meanings
  6. Handle pluralization: Use separate keys for singular/plural forms
  7. Provide fallbacks: Always have default translations for missing keys

Migration Notes

When adding I18n to existing components:

  • Replace hardcoded strings with translation keys
  • Update layouts to use directional modifiers
  • Test with both LTR and RTL languages
  • Consider text direction for form inputs
  • Update icon usage to be direction-aware

See Also