• Stars
    star
    677
  • Rank 66,694 (Top 2 %)
  • Language
    Kotlin
  • License
    MIT License
  • Created over 3 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🌎 The missing I18N/L10N (internationalization/localization) multiplatform library for Jetpack Compose!

Maven metadata URL Android API kotlin ktlint License MIT

Lyricist 🌎🌍🌏

The missing I18N and L10N multiplatform library for Jetpack Compose!

Jetpack Compose greatly improved the way we build UIs on Android, but not how we interact with strings. stringResource() works well, but doesn't benefit from the idiomatic Kotlin like Compose.

Lyricist tries to make working with strings as powerful as building UIs with Compose, i.e., working with parameterized string is now typesafe, use of when expression to work with plurals with more flexibility, and even load/update the strings dynamically via an API!

Features

Limitations

  • The XML processor doesn't handle few and many plural values (PRs are welcome)

Why Lyricist?

Inspired by accompanist library: music composing is done by a composer, and since this library is about writing lyrics strings, the role of a lyricist felt like a good name.

Usage

Take a look at the sample app and sample-multi-module for working examples.

Start by declaring your strings on a data class, class or interface (pick one). The strings can be anything (really, it's up to you): Char, String, AnnotatedString, List<String>, Set<String> or even lambdas!

data class Strings(
    val simple: String,
    val annotated: AnnotatedString,
    val parameter: (locale: String) -> String,
    val plural: (count: Int) -> String,
    val list: List<String>
)

Next, create instances for each supported language and annotate with @LyricistStrings. The languageTag must be an IETF BCP47 compliant language tag (docs). You must flag one of them as default.

@LyricistStrings(languageTag = Locales.EN, default = true)
val EnStrings = Strings(
    simple = "Hello Compose!",

    annotated = buildAnnotatedString {
        withStyle(SpanStyle(color = Color.Red)) { 
            append("Hello ") 
        }
        withStyle(SpanStyle(fontWeight = FontWeight.Light)) { 
            append("Compose!") 
        }
    },

    parameter = { locale ->
        "Current locale: $locale"
    },

    plural = { count ->
        val value = when (count) {
            1, 2 -> "few"
            in 3..10 -> "bunch of"
            else -> "lot of"
        }
        "I have a $value apples"
    },

    list = listOf("Avocado", "Pineapple", "Plum")
)

@LyricistStrings(languageTag = Locales.PT)
val PtStrings = Strings(/* pt strings */)

@LyricistStrings(languageTag = Locales.ES)
val EsStrings = Strings(/* es strings */)

@LyricistStrings(languageTag = Locales.RU)
val RuStrings = Strings(/* ru strings */)

Lyricist will generate the LocalStrings property, a CompositionLocal that provides the strings of the current locale. It will also generate rememberStrings() and ProvideStrings(), call them to make LocalStrings accessible down the tree.

val lyricist = rememberStrings()

ProvideStrings(lyricist) {
    // Content
}

// Or just 
ProvideStrings {
    // Content
}
Writing the code for yourself

Don't want to enable KSP to generate the code for you? No problem! Follow the steps below to integrate with Lyricist manually.

First, map each supported language tag to their corresponding instances.

val strings = mapOf(
    Locales.EN to EnStrings,
    Locales.PT to PtStrings,
    Locales.ES to EsStrings,
    Locales.RU to RuStrings
)

Next, create your LocalStrings and choose one translation as default.

val LocalStrings = staticCompositionLocalOf { EnStrings }

Finally, use the same functions, rememberStrings() and ProvideStrings(), to make your LocalStrings accessible down the tree. But this time you need to provide your strings and LocalStrings manually.

val lyricist = rememberStrings(strings)

ProvideStrings(lyricist, LocalStrings) {
    // Content
}

Now you can use LocalStrings to retrieve the current strings.

val strings = LocalStrings.current

Text(text = strings.simple)
// > Hello Compose!

Text(text = strings.annotated)
// > Hello Compose!

Text(text = strings.parameter(lyricist.languageTag))
// > Current locale: en

Text(text = strings.plural(1))
Text(text = strings.plural(5))
Text(text = strings.plural(20))
// > I have a few apples
// > I have a bunch of apples
// > I have a lot of apples

Text(text = strings.list.joinToString())
// > Avocado, Pineapple, Plum

Use the Lyricist instance provided by rememberStrings() to change the current locale. This will trigger a recomposition that will update the strings wherever they are being used.

lyricist.languageTag = Locales.PT

Controlling the visibility

To control the visibility (public or internal) of the generated code, provide the following (optional) argument to KSP in the module's build.gradle.

ksp {
    arg("lyricist.internalVisibility", "true")
}

Important: Lyricist uses the System locale as default. It won't persist the current locale on storage, is outside its scope.

Multi module settings

If you are using Lyricist on a multi module project and the generated declarations (LocalStrings, rememberStrings(), ProvideStrings()) are too generic for you, provide the following (optional) arguments to KSP in the module's build.gradle.

ksp {
    arg("lyricist.packageName", "com.my.app")
    arg("lyricist.moduleName", project.name)
}

Let's say you have a "dashboard" module, the generated declarations will be LocalDashboardStrings, rememberDashboardStrings() and ProvideDashboardStrings().

Migrating from strings.xml

So you liked Lyricist, but already have a project with thousands of strings spread over multiples files? I have good news for you: Lyricist can extract these existing strings and generate all the code you just saw above.

Similar to the multi module setup, you must provide a few arguments to KSP. Lyricist will search for strings.xml files in the resources path. You can also provide a language tag to be used as default value for the LocalStrings.

ksp {
    // Required
    arg("lyricist.xml.resourcesPath", android.sourceSets.main.res.srcDirs.first().absolutePath)
    
    // Optional
    arg("lyricist.packageName", "com.my.app")
    arg("lyricist.xml.moduleName", "xml")
    arg("lyricist.xml.defaultLanguageTag", "en")
}

After the first build, the well-known rememberStrings() and ProvideStrings() (naming can vary depending on your KSP settings) will be available for use. Lyricist will also generated a Locales object containing all language tags currently in use in your project.

val lyricist = rememberStrings(strings)

ProvideStrings(lyricist, LocalStrings) {
    // Content
}

lyricist.languageTag = Locales.PT

You can easily migrate from strings.xml to Lyricist just by copying the generated files to your project. That way, you can finally say goodbye to strings.xml.

Troubleshooting

Can't use the generated code on my IDE

You should set manually the source sets of the generated files, like described here.

buildTypes {
    debug {
        sourceSets {
            main.java.srcDirs += 'build/generated/ksp/debug/kotlin/'
        }
    }
    release {
        sourceSets {
            main.java.srcDirs += 'build/generated/ksp/release/kotlin/'
        }
    }
}

Import to your project

  1. Importing the KSP plugin in the project's build.gradle then apply to your module's build.gradle
plugins {
    id("com.google.devtools.ksp") version "${ksp-latest-version}"
}
  1. Add the desired dependencies to your module's build.gradle
// Required
implementation("cafe.adriel.lyricist:lyricist:${latest-version}")

// If you want to use @LyricistStrings to generate code for you
ksp("cafe.adriel.lyricist:lyricist-processor:${latest-version}")

// If you want to migrate from strings.xml
ksp("cafe.adriel.lyricist:lyricist-processor-xml:${latest-version}")

Multiplatform setup

Doing code generation only at commonMain. Currently workaround, for more information see KSP Issue 567

dependencies {
    add("kspCommonMainMetadata", "cafe.adriel.lyricist:lyricist-processor:${latest-version}")
}

tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>>().all {
    if(name != "kspCommonMainKotlinMetadata") {
        dependsOn("kspCommonMainKotlinMetadata")
    }
}

kotlin.sourceSets.commonMain {
    kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
}

Version Catalog

[versions]
lyricist = {latest-version}

[libraries]
lyricist-library = { module = "cafe.adriel.lyricist:lyricist", version.ref = "lyricist" }
lyricist-processor = { module = "cafe.adriel.lyricist:lyricist-processor", version.ref = "lyricist" }
lyricist-processorXml = { module = "cafe.adriel.lyricist:lyricist-processor-xml", version.ref = "lyricist" }

Current version: Maven metadata URL

More Repositories

1

voyager

🛸 A pragmatic navigation library for Jetpack Compose
Kotlin
2,466
star
2

AndroidAudioRecorder

A fancy audio recorder lib for Android. Supports WAV format at 48kHz.
Java
1,622
star
3

AndroidAudioConverter

Convert audio files inside your Android app easily. Supported formats: AAC, MP3, M4A, WMA, WAV and FLAC.
Java
1,319
star
4

bonsai

🌳 A multiplatform tree view for Jetpack Compose
Kotlin
350
star
5

krumbsview

🍞 The ultimate breadcrumbs view for Android!
Kotlin
186
star
6

chroma

🎶 Chromatic tuner app for Android
Kotlin
111
star
7

pufferdb

🐡 An Android & JVM key-value storage powered by Protobuf and Coroutines
Kotlin
100
star
8

broker

💬 Publish-Subscribe (a.k.a Pub/Sub, EventBus) library for Android and JVM built with Coroutines
Kotlin
95
star
9

AndroidOAuth

A simple way to authenticate with Google and Facebook using OAuth 2.0 in Android
Java
88
star
10

hal

🔴 A non-deterministic finite-state machine for Android & JVM that won't let you down
Kotlin
79
star
11

satchel

🎒 A fast, secure and modular key-value storage with batteries-included for Android and JVM.
Kotlin
71
star
12

KBus

Dead simple EventBus for Android made with Kotlin and RxJava 2
Kotlin
46
star
13

NoMansWallpaperApp

Looking for your next No Man's Sky wallpaper?
Kotlin
38
star
14

kaptain

👨‍✈️ multi-module navigation on Android has never been so easier!
Kotlin
27
star
15

AndroidStreamable

Unofficial https://streamable.com API Wrapper for Android
Java
27
star
16

dalek

🤖 UI driven state machine for Android & JVM that will exterminate your bugs
Kotlin
26
star
17

CrypAndroidApp

A cryptocurrency portfolio tracker app for Android
Kotlin
26
star
18

NMSAlphabetAndroidApp

An unofficial translator app for No Man's Sky
Java
25
star
19

AndroidCoroutineScopes

This lib implements the most common CoroutineScopes used in Android apps.
Kotlin
17
star
20

gwent-wallpapers

🃏🐺Unofficial Android app with more than 1.000 cards from GWENT: The Witcher Card Game as mobile wallpapers
Kotlin
16
star
21

ReadnBuyAndroidApp

Android app developed at the Vanhackathon for Shopify's challenge. Coded with Kotlin, RxJava and MVP.
Kotlin
16
star
22

BeMyEyesXamarinApp

Describes photos via audio to the visually impaired user
C#
13
star
23

VoxRecorderAndroidApp

An audio recorder app for Android built with Kotlin, RxJava and MVP
Kotlin
10
star
24

MoovAndroidApp

Concept Android app
Kotlin
9
star
25

GreenHellCompanionApp

Unofficial companion app for the game Green Hell
Kotlin
4
star
26

gwent-cards

🃏🐺 All cards from GWENT: The Witcher Card Game
4
star
27

RecifeBomDeBola

Versão mobile de https://recifebomdebola.wordpress.com
Java
3
star
28

cryp-website

Cryp app's website
HTML
2
star
29

MinhaEscola

Minha Escola é um aplicativo Android que pode ser usado por qualquer escola para exibir os horários e histórico escolar de seus alunos.
Java
2
star
30

Scryp

REST API powered by Moneywagon: the universal cryptocurrency multichain library
Python
2
star
31

S-Task

Just a simple, beautiful, offline and open source To Do list for Android 4.0.3+
Java
2
star
32

VaiChoverRecife

App para Firefox OS
JavaScript
2
star
33

PlaystationTrophiesConceptApp

Concept Android app
Kotlin
2
star
34

NMSAlphabetWeb

An unofficial Android app for No Man's Sky (WIP)
CSS
2
star
35

LifeBand

An app + wearable device that can change your life
Java
1
star
36

CryptoUtil

Utility class for encryption
C#
1
star
37

responsive-headings.css

Bring responsiveness to HTML headings
CSS
1
star
38

GuiaServicosSP

Guia de Serviços SP é um aplicativo Android que ajuda os cidadãos paulistas a encontrarem informações sobre serviços públicos do estado de São Paulo mais facilmente.
Java
1
star
39

adriel.cafe

1
star
40

MessagesAndroidApp

A simple app to showcase Kotlin, MVP, RxJava, Realm, Moshi
Kotlin
1
star
41

ChuckNorrisFacts

A Chuck Norris facts Android app
Kotlin
1
star
42

GryphonFramework

A Framework for integrating ontologies and relational databases
Web Ontology Language
1
star
43

DissertacaoDeMestrado

Dissertação de Mestrado defendida por Adriel Café no CIn-UFPE em 04/09/15
1
star
44

PlanetaSustentavel

Planeta Sustentável é um aplicativo Android com diversas dicas essências de práticas sustentáveis. Aprenda a consumir menos recursos da natureza e torne-se um cidadão melhor!
Java
1
star