Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add layout direction support to library #50

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions android-module.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ android {
kotlinCompilerExtensionVersion libs.versions.composeCompiler.get()
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ plugin-android = "8.2.2"
plugin-ktlint = "12.1.0"
plugin-maven = "0.28.0"

kotlin = "1.9.22"
kotlin = "1.9.23"

coroutines = "1.8.0"
ksp = "1.9.22-1.0.17"
ksp = "1.9.23-1.0.20"
caseFormat = "0.2.0"
konsumeXml = "1.1"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ package cafe.adriel.lyricist

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.unit.LayoutDirection as ComposeLayoutDirection

@Composable
public fun <T> rememberStrings(
translations: Map<LanguageTag, T>,
layoutDirections: Map<LanguageTag, LayoutDirection>,
defaultLanguageTag: LanguageTag = "en",
currentLanguageTag: LanguageTag = Locale.current.toLanguageTag()
): Lyricist<T> =
remember(defaultLanguageTag) {
Lyricist(defaultLanguageTag, translations)
Lyricist(defaultLanguageTag, layoutDirections, translations)
}.apply {
languageTag = currentLanguageTag
}
Expand All @@ -31,6 +33,14 @@ public fun <T> ProvideStrings(

CompositionLocalProvider(
provider provides state.strings,
LocalLayoutDirection provides state.layoutDirection.toComposeLayoutDirection(),
content = content
)
}

private fun LayoutDirection.toComposeLayoutDirection(): ComposeLayoutDirection {
return when (this) {
LayoutDirection.Ltr -> ComposeLayoutDirection.Ltr
LayoutDirection.Rtl -> ComposeLayoutDirection.Rtl
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,36 @@ public typealias LanguageTag = String

public class Lyricist<T>(
private val defaultLanguageTag: LanguageTag,
private val layoutDirections: Map<LanguageTag, LayoutDirection>,
private val translations: Map<LanguageTag, T>
) {

private val mutableState: MutableStateFlow<LyricistState<T>> =
MutableStateFlow(LyricistState(defaultLanguageTag, getStrings(defaultLanguageTag)))
MutableStateFlow(
LyricistState(
defaultLanguageTag,
getLayoutDirection(defaultLanguageTag),
getStrings(defaultLanguageTag)
)
)

public val state: StateFlow<LyricistState<T>> =
mutableState.asStateFlow()

public var languageTag: LanguageTag
get() = mutableState.value.languageTag
set(languageTag) {
mutableState.value = LyricistState(languageTag, getStrings(languageTag))
mutableState.value = LyricistState(
languageTag,
getLayoutDirection(languageTag),
getStrings(languageTag)
)
}

public val layoutDirection: LayoutDirection
get() = mutableState.value.layoutDirection


public val strings: T
get() = mutableState.value.strings

Expand All @@ -35,13 +50,20 @@ public class Lyricist<T>(
?: translations[defaultLanguageTag]
?: throw LyricistException("Strings for language tag $languageTag not found")

private fun getLayoutDirection(languageTag: LanguageTag): LayoutDirection =
layoutDirections[languageTag]
?: layoutDirections[languageTag.fallback]
?: layoutDirections[defaultLanguageTag]
?: throw LyricistException("LayoutDirection for language tag $languageTag not found")

private companion object {
private val FALLBACK_REGEX = Regex("[-_]")
}
}

public data class LyricistState<T> internal constructor(
val languageTag: LanguageTag,
val layoutDirection: LayoutDirection,
val strings: T,
)

Expand All @@ -53,5 +75,10 @@ public class LyricistException internal constructor(
@Retention(AnnotationRetention.SOURCE)
public annotation class LyricistStrings(
val languageTag: LanguageTag,
val layoutDirection: LayoutDirection = LayoutDirection.Ltr,
val default: Boolean = false
)

public enum class LayoutDirection {
Ltr, Rtl
}
1 change: 1 addition & 0 deletions lyricist-processor-compose/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ apply from: "../kotlin-module.gradle"
dependencies {
implementation libs.ksp
implementation libs.caseFormat
implementation project(':lyricist-core')
}

apply plugin: "com.vanniktech.maven.publish"
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ internal class LyricistSymbolProcessor(

val stringsName = "${config.moduleName.toLowerCamelCase()}Strings"

val layoutDirectionsName = "${config.moduleName.toLowerCamelCase()}LayoutDirections"

val visibility = if (config.internalVisibility) "internal" else "public"

val stringsProperty = if (config.generateStringsProperty) {
Expand Down Expand Up @@ -79,6 +81,15 @@ internal class LyricistSymbolProcessor(
"$INDENTATION\"$languageTag\" to $property"
}

val layoutDirectionMappingOutput = roundDeclaration
.map {
val languageTag = it.annotations.getValue<String>(ANNOTATION_PARAM_LANGUAGE_TAG)!!
val layoutDirection = it.annotations.getValue<Any>(ANNOTATION_PARAM_LAYOUT_DIRECTION)!!
languageTag to layoutDirection
}.joinToString(",\n") { (languageTag, layoutDirection) ->
"$INDENTATION\"$languageTag\" to $layoutDirection"
}

codeGenerator.createNewFile(
dependencies = Dependencies(
aggregating = true,
Expand All @@ -97,6 +108,7 @@ internal class LyricistSymbolProcessor(
|import androidx.compose.ui.text.intl.Locale
|import cafe.adriel.lyricist.Lyricist
|import cafe.adriel.lyricist.LanguageTag
|import cafe.adriel.lyricist.LayoutDirection
|import cafe.adriel.lyricist.rememberStrings
|import cafe.adriel.lyricist.ProvideStrings
|$packagesOutput
Expand All @@ -105,6 +117,10 @@ internal class LyricistSymbolProcessor(
|$translationMappingOutput
|)
|
|$visibility val $layoutDirectionsName: Map<LanguageTag, LayoutDirection> = mapOf(
|$layoutDirectionMappingOutput
|)
|
|$visibility val Local$fileName: ProvidableCompositionLocal<$stringsClassOutput> =
| staticCompositionLocalOf { $defaultStringsOutput }
|
Expand All @@ -115,7 +131,7 @@ internal class LyricistSymbolProcessor(
| defaultLanguageTag: LanguageTag = $defaultLanguageTag,
| currentLanguageTag: LanguageTag = Locale.current.toLanguageTag(),
|): Lyricist<$stringsClassOutput> =
| rememberStrings($stringsName, defaultLanguageTag, currentLanguageTag)
| rememberStrings($stringsName, $layoutDirectionsName, defaultLanguageTag, currentLanguageTag)
|
|@Composable
|$visibility fun Provide$fileName(
Expand Down Expand Up @@ -204,6 +220,7 @@ internal class LyricistSymbolProcessor(
const val ANNOTATION_NAME = "LyricistStrings"
const val ANNOTATION_PACKAGE = "cafe.adriel.lyricist.$ANNOTATION_NAME"
const val ANNOTATION_PARAM_LANGUAGE_TAG = "languageTag"
const val ANNOTATION_PARAM_LAYOUT_DIRECTION = "layoutDirection"
const val ANNOTATION_PARAM_DEFAULT = "default"
}
}
2 changes: 2 additions & 0 deletions lyricist-processor-xml/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
apply from: "../kotlin-module.gradle"

dependencies {
implementation(project(":lyricist-core"))

implementation libs.ksp
implementation libs.caseFormat
implementation libs.konsumeXml
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cafe.adriel.lyricist.processor.xml.internal

import cafe.adriel.lyricist.LayoutDirection
import cafe.adriel.lyricist.processor.xml.internal.ktx.INDENTATION
import cafe.adriel.lyricist.processor.xml.internal.ktx.filterXmlStringFiles
import cafe.adriel.lyricist.processor.xml.internal.ktx.formatted
Expand All @@ -15,7 +16,9 @@ import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated
import java.awt.ComponentOrientation
import java.io.File
import java.util.Locale

internal class LyricistXmlSymbolProcessor(
private val config: LyricistXmlConfig,
Expand Down Expand Up @@ -52,10 +55,18 @@ internal class LyricistXmlSymbolProcessor(
val fileName = "${config.moduleName.toUpperCamelCase()}Strings"

val stringsName = "${config.moduleName.toLowerCamelCase()}Strings"
val layoutDirectionsName = "${config.moduleName.toLowerCamelCase()}LayoutDirections"

strings[config.defaultLanguageTag]
?.let { writeStringsClassFile(fileName, stringsName, it, strings.keys) }
?: logger.error("Default language tag not found")
?.let {
writeStringsClassFile(
fileName,
stringsName,
layoutDirectionsName,
it,
strings.keys
)
} ?: logger.error("Default language tag not found")

val defaultStrings = strings[config.defaultLanguageTag].orEmpty()

Expand All @@ -74,6 +85,7 @@ internal class LyricistXmlSymbolProcessor(
private fun writeStringsClassFile(
fileName: String,
stringsName: String,
layoutDirectionsName: String,
strings: StringResources,
languageTags: Set<String>
) {
Expand All @@ -88,6 +100,7 @@ internal class LyricistXmlSymbolProcessor(
"(${params.joinToString()}) -> String"
}
}

is StringResource.StringArray -> "List<String>"
is StringResource.Plurals -> "(quantity: Int) -> String"
}
Expand All @@ -102,6 +115,15 @@ internal class LyricistXmlSymbolProcessor(
"${INDENTATION}Locales.${languageTag.toUpperCamelCase()} to $property"
}

val layoutDirectionMappingOutput = languageTags
.map { languageTag ->
val isLtr = ComponentOrientation.getOrientation(Locale(languageTag)).isLeftToRight
val layoutDirection = if (isLtr) LayoutDirection.Ltr else LayoutDirection.Rtl
languageTag to layoutDirection
}.joinToString(",\n") { (languageTag, layoutDirection) ->
"$INDENTATION\"$languageTag\" to LayoutDirection.$layoutDirection"
}

val localesOutput = languageTags
.joinToString("\n") { languageTag ->
"${INDENTATION}val ${languageTag.toUpperCamelCase()} = \"$languageTag\""
Expand Down Expand Up @@ -130,6 +152,7 @@ internal class LyricistXmlSymbolProcessor(
|import androidx.compose.ui.text.intl.Locale
|import cafe.adriel.lyricist.Lyricist
|import cafe.adriel.lyricist.LanguageTag
|import cafe.adriel.lyricist.LayoutDirection
|import cafe.adriel.lyricist.rememberStrings
|import cafe.adriel.lyricist.ProvideStrings
|
Expand All @@ -155,6 +178,10 @@ internal class LyricistXmlSymbolProcessor(
|
|public val $stringsName: Map<LanguageTag, $fileName> = mapOf(
|$translationMappingOutput
|)
|
|public val $layoutDirectionsName: Map<LanguageTag, LayoutDirection> = mapOf(
|$layoutDirectionMappingOutput
|)
|
|public val Local$fileName: ProvidableCompositionLocal<$fileName> =
Expand All @@ -165,7 +192,7 @@ internal class LyricistXmlSymbolProcessor(
| defaultLanguageTag: LanguageTag = "${config.defaultLanguageTag}",
| currentLanguageTag: LanguageTag = Locale.current.toLanguageTag(),
|): Lyricist<$fileName> =
| rememberStrings($stringsName, defaultLanguageTag, currentLanguageTag)
| rememberStrings($stringsName, $layoutDirectionsName, defaultLanguageTag, currentLanguageTag)
|
|@Composable
|public fun Provide$fileName(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cafe.adriel.lyricist.sample.multimodule.strings

import cafe.adriel.lyricist.LayoutDirection
import cafe.adriel.lyricist.LyricistStrings

@LyricistStrings(languageTag = "fa", layoutDirection = LayoutDirection.Rtl)
val FaMultiModuleStrings = MultiModuleStrings(
string = "سلام کامپوز!"
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package cafe.adriel.lyricist.sample.multimodule.strings

import cafe.adriel.lyricist.LayoutDirection
import cafe.adriel.lyricist.LyricistStrings

@LyricistStrings(languageTag = "pt")
@LyricistStrings(languageTag = "pt", layoutDirection = LayoutDirection.Ltr)
val PtMultiModuleStrings = MultiModuleStrings(
string = "Olá Compose!"
)
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ internal fun SampleApplication() {
Locales.PT,
Modifier.weight(1f)
)
Spacer(Modifier.weight(.1f))
SwitchLocaleButton(
lyricist,
Locales.FA,
Modifier.weight(1f)
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package cafe.adriel.lyricist.sample.multiplatform
object Locales {
const val EN = "en"
const val PT = "pt"
const val FA = "fa"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import cafe.adriel.lyricist.LayoutDirection
import cafe.adriel.lyricist.LyricistStrings
import cafe.adriel.lyricist.sample.multiplatform.Locales

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

Expand Down
Loading