Constants for language access

This commit is contained in:
Alexander Nozik 2023-01-09 11:24:13 +03:00
parent f4201bea7a
commit da7cf45f8a
4 changed files with 160 additions and 143 deletions

View File

@ -4,12 +4,8 @@ import kotlinx.html.body
import kotlinx.html.head import kotlinx.html.head
import kotlinx.html.title import kotlinx.html.title
import space.kscience.dataforge.data.Data import space.kscience.dataforge.data.Data
import space.kscience.dataforge.data.getItem
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.removeHeadOrNull
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
/** /**
@ -21,63 +17,24 @@ public interface DataRenderer {
public operator fun invoke(name: Name, data: Data<Any>) public operator fun invoke(name: Name, data: Data<Any>)
public companion object { public companion object {
// context (SiteBuilder)
// public fun buildPageMeta(name: Name, data: Data<Any>): Laminate {
// val languages = languages.mapKeys { it.value["key"]?.string ?: it.key }
//
// // detect current language by prefix if it is not defined explicitly
// val currentLanguage: String = data.meta["language"]?.string
// ?: languages.keys.firstOrNull() { key -> name.startsWith(key.parseAsName()) } ?: defaultLanguage
//
// //
// val languageMap = Meta {
// languages.forEach { (key, meta) ->
// val languagePrefix: String = meta.string ?: meta["name"]?.string ?: return@forEach
// val targetName = name.removeHeadOrNull("")
// val targetData = this@SiteBuilder.data[targetName.parseAsName()]
// if (targetData != null) key put targetName
// }
// }
// val languageMeta = Meta {
// "language" put currentLanguage
// if (!languageMap.isEmpty()) {
// "languageMap" put languageMap
// }
// }
// return Laminate(data.meta, languageMeta, siteMeta)
// }
/**
* Automatically build a language map for a data piece with given [name] based on existence of appropriate data nodes.
*/
context(SiteBuilder)
public fun buildLanguageMeta(name: Name): Meta = Meta {
val currentLanguagePrefix = languages[language]?.get("prefix")?.string ?: language
val fullName = (route.removeHeadOrNull(currentLanguagePrefix.asName()) ?: route) + name
languages.forEach { (key, meta) ->
val languagePrefix: String = meta["prefix"].string ?: key
val nameWithLanguage: Name = if (languagePrefix.isBlank()) {
fullName
} else {
languagePrefix.asName() + fullName
}
if (data.getItem(name) != null) {
key put meta.asMutableMeta().apply {
"target" put nameWithLanguage.toString()
}
}
}
}
public val DEFAULT: DataRenderer = object : DataRenderer { public val DEFAULT: DataRenderer = object : DataRenderer {
context(SiteBuilder) context(SiteBuilder)
override fun invoke(name: Name, data: Data<Any>) { override fun invoke(name: Name, data: Data<Any>) {
if (data.type == typeOf<HtmlData>()) { if (data.type == typeOf<HtmlData>()) {
page(name, data.meta) { val languageMeta: Meta = Language.forName(name)
val dataMeta: Meta = if (languageMeta.isEmpty()) {
data.meta
} else {
data.meta.toMutableMeta().apply {
"languages" put languageMeta
}
}
page(name, dataMeta) {
head { head {
title = data.meta["title"].string ?: "Untitled page" title = dataMeta["title"].string ?: "Untitled page"
} }
body { body {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View File

@ -0,0 +1,147 @@
package space.kscience.snark.html
import space.kscience.dataforge.data.getItem
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.*
import space.kscience.snark.html.Language.Companion.SITE_LANGUAGES_KEY
import space.kscience.snark.html.Language.Companion.SITE_LANGUAGE_KEY
public class Language : Scheme() {
/**
* Language key override
*/
public var key: String? by string()
/**
* Page name prefix
*/
public var prefix: String? by string()
/**
* Target page name with a given language key
*/
public var target: Name?
get() = meta["target"].string?.parseAsName(false)
set(value) {
meta["target"] = value?.toString()?.asValue()
}
public companion object : SchemeSpec<Language>(::Language) {
public val LANGUAGE_KEY: Name = "language".asName()
public val LANGUAGES_KEY: Name = "languages".asName()
public val SITE_LANGUAGE_KEY: Name = SiteBuilder.SITE_META_KEY + LANGUAGE_KEY
public val SITE_LANGUAGES_KEY: Name = SiteBuilder.SITE_META_KEY + LANGUAGES_KEY
public const val DEFAULT_LANGUAGE: String = "en"
/**
* Automatically build a language map for a data piece with given [name] based on existence of appropriate data nodes.
*/
context(SiteBuilder)
public fun forName(name: Name): Meta = Meta {
val currentLanguagePrefix = languages[language]?.get(Language::prefix.name)?.string ?: language
val fullName = (route.removeHeadOrNull(currentLanguagePrefix.asName()) ?: route) + name
languages.forEach { (key, meta) ->
val languagePrefix: String = meta[Language::prefix.name].string ?: key
val nameWithLanguage: Name = if (languagePrefix.isBlank()) {
fullName
} else {
languagePrefix.asName() + fullName
}
if (data.getItem(name) != null) {
key put meta.asMutableMeta().apply {
Language::target.name put nameWithLanguage.toString()
}
}
}
}
}
}
public val SiteBuilder.languages: Map<String, Meta>
get() = siteMeta[SITE_LANGUAGES_KEY]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
public val SiteBuilder.language: String
get() = siteMeta[SITE_LANGUAGE_KEY].string ?: Language.DEFAULT_LANGUAGE
public val SiteBuilder.languagePrefix: Name
get() = languages[language]?.let { it[Language::prefix.name].string ?: language }?.parseAsName() ?: Name.EMPTY
public fun SiteBuilder.withLanguages(languageMap: Map<String, Meta>, block: SiteBuilder.(language: String) -> Unit) {
languageMap.forEach { (languageKey, languageMeta) ->
val prefix = languageMeta[Language::prefix.name].string ?: languageKey
val routeMeta = Meta {
SITE_LANGUAGE_KEY put languageKey
SITE_LANGUAGES_KEY put Meta {
languageMap.forEach {
it.key put it.value
}
}
}
route(prefix, routeMeta = routeMeta) {
block(languageKey)
}
}
}
public fun SiteBuilder.withLanguages(
vararg language: Pair<String, String>,
block: SiteBuilder.(language: String) -> Unit,
) {
val languageMap = language.associate {
it.first to Meta {
Language::prefix.name put it.second
}
}
withLanguages(languageMap, block)
}
/**
* The language key of this page
*/
public val WebPage.language: String
get() = pageMeta[Language.LANGUAGE_KEY]?.string ?: pageMeta[SITE_LANGUAGE_KEY]?.string ?: Language.DEFAULT_LANGUAGE
/**
* Mapping of language keys to other language versions of this page
*/
public val WebPage.languages: Map<String, Meta>
get() = pageMeta[Language.LANGUAGES_KEY]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
public fun WebPage.localisedPageRef(pageName: Name, relative: Boolean = false): String {
val prefix = languages[language]?.get(Language::prefix.name)?.string?.parseAsName() ?: Name.EMPTY
return resolvePageRef(prefix + pageName, relative)
}
/**
* Render all pages in a node with given name. Use localization prefix if appropriate data is available.
*/
public fun SiteBuilder.localizedPages(
dataPath: Name,
remotePath: Name = dataPath,
dataRenderer: DataRenderer = DataRenderer.DEFAULT,
) {
val item = data.getItem(languagePrefix + dataPath)
?: data.getItem(dataPath)
?: error("No data found by name $dataPath")
route(remotePath) {
pages(item, dataRenderer)
}
}
public fun SiteBuilder.localizedPages(
dataPath: String,
remotePath: Name = dataPath.parseAsName(),
dataRenderer: DataRenderer = DataRenderer.DEFAULT,
) {
localizedPages(dataPath.parseAsName(), remotePath, dataRenderer = dataRenderer)
}

View File

@ -89,6 +89,7 @@ public interface SiteBuilder : ContextAware, SnarkContext {
public companion object { public companion object {
public val SITE_META_KEY: Name = "site".asName()
public val INDEX_PAGE_TOKEN: NameToken = NameToken("index") public val INDEX_PAGE_TOKEN: NameToken = NameToken("index")
public val UP_PAGE_TOKEN: NameToken = NameToken("..") public val UP_PAGE_TOKEN: NameToken = NameToken("..")
} }

View File

@ -1,88 +0,0 @@
package space.kscience.snark.html
import space.kscience.dataforge.data.getItem
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.plus
public val SiteBuilder.languages: Map<String, Meta>
get() = siteMeta["site.languages"]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
public val SiteBuilder.language: String
get() = siteMeta["site.language"].string ?: "en"
public val SiteBuilder.languagePrefix: Name
get() = languages[language]?.let { it["prefix"].string ?: language }?.parseAsName() ?: Name.EMPTY
public fun SiteBuilder.withLanguages(languageMap: Map<String, Meta>, block: SiteBuilder.(language: String) -> Unit) {
languageMap.forEach { (languageKey, languageMeta) ->
val prefix = languageMeta["prefix"].string ?: languageKey
val routeMeta = Meta {
"site.language" put languageKey
"site.languages" put Meta {
languageMap.forEach {
it.key put it.value
}
}
}
route(prefix, routeMeta = routeMeta) {
block(languageKey)
}
}
}
public fun SiteBuilder.withLanguages(
vararg language: Pair<String, String>,
block: SiteBuilder.(language: String) -> Unit,
) {
val languageMap = language.associate {
it.first to Meta {
"prefix" put it.second
}
}
withLanguages(languageMap, block)
}
/**
* The language key of this page
*/
public val WebPage.language: String get() = pageMeta["language"]?.string ?: pageMeta["site.language"]?.string ?: "en"
/**
* Mapping of language keys to other language versions of this page
*/
public val WebPage.languages: Map<String, Meta>
get() = pageMeta["languages"]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
public fun WebPage.localisedPageRef(pageName: Name, relative: Boolean = false): String {
val prefix = languages[language]?.get("prefix")?.string?.parseAsName() ?: Name.EMPTY
return resolvePageRef(prefix + pageName, relative)
}
/**
* Render all pages in a node with given name. Use localization prefix if appropriate data is available.
*/
public fun SiteBuilder.localizedPages(
dataPath: Name,
remotePath: Name = dataPath,
dataRenderer: DataRenderer = DataRenderer.DEFAULT,
) {
val item = data.getItem(languagePrefix + dataPath)
?: data.getItem(dataPath)
?: error("No data found by name $dataPath")
route(remotePath) {
pages(item, dataRenderer)
}
}
public fun SiteBuilder.localizedPages(
dataPath: String,
remotePath: Name = dataPath.parseAsName(),
dataRenderer: DataRenderer = DataRenderer.DEFAULT,
) {
localizedPages(dataPath.parseAsName(), remotePath, dataRenderer = dataRenderer)
}