Internationalization in Ktor based website - kotlin

I'm new to Ktor and Kotlin in general, so please be patient.
I am currently trying to create a little website (mostly for learning) that uses key-value files for internationalization.
I already did something similar in PHP where I just decoded a JSON file and got the value related to the key I passed. This way, I could do something as <p><?php echo $langJson["presentation"][0];?></p> (with $langJson being my json key-value file) so that I would get the proper translation.
I'm trying to do an equivalent in Kotlin using Ktor, but I don't know how to do it. I found the aymanizz ktor-i18n plugin on GitHub that allows to use i18n for internationalization but I don't know if it is really adapted to what I want to do since it detects the language in the header instead of it being chose by the user (with _GET for instance).
Does anyone have any clue on how I could do that?
Briefly, what I want to do is having a single coded page where the content is dynamicly chosen from the accurate language file.
Thank you all! :)

The basic idea is to get a language code from a request (a query parameter, a header, etc), generate a path to an i18n resource file, read it and then deserialize JSON into a map. The resulting map could be used as-is or passed as a model to a template.
Here is an example where I use kotlinx.serialization to transform a JSON string to get a map and FreeMarker template engine to render HTML. To switch a language just use the lang GET parameter, e.g, http://localhost:8080/?lang=es.
import freemarker.cache.ClassTemplateLoader
import io.ktor.application.*
import io.ktor.freemarker.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
fun main() {
embeddedServer(Netty, port = 8080) {
install(FreeMarker) {
templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
}
routing {
get("/") {
call.respond(FreeMarkerContent("index.ftl", mapOf("i18n" to loadI18n(call.request))))
}
}
}.start()
}
fun loadI18n(request: ApplicationRequest): Map<String, String> {
val language = request.queryParameters["lang"] ?: "en"
val filePath = "i18n/$language.json"
val data = object {}.javaClass.classLoader.getResource(filePath)?.readText() ?: error("Cannot load i18n from $filePath")
return Json.decodeFromString(data)
}
resources/templates/index.ftl
<html>
<body>
<h1>${i18n.greetings}</h1>
</body>
</html>
resources/i18n/en.json
{
"greetings": "Hello"
}
resources/i18n/es.json
{
"greetings": "Hola"
}

If you want to fully support of i18n, i recomand to use https://github.com/aymanizz/ktor-i18n. You will have ability to use plulars and other thinks from i18n standart.

Related

How to start a #Bean with custom parameters after an event had happened with spring for tests?

I am working on adding RepositoryTests with TestContainers framework for a project that uses R2dbc and I am running into the following situation:
1 - On the main project I set r2dbc url (with port and hostname) on application.yaml file and spring data manages everything and things just work.
2 - On the Tests however, I am using TestContainers framework more specifically DockerComposeContainer which I use to create a mocked container using docker-compose.test.yaml file with the databases I need.
3 - This container creates a port number on the go I define a port number on my docker-compose file but the port number that DockerComposeContainer will provide me is random and changes everytime I run the tests, what makes having a static url on application-test.yaml not an option anymore.
So I need to dinamically create this bean R2dbcEntityTemplate at run time and only after the DockerComposeContainer will give me the port number. So my application can connect to the correct port and things should work as expected.
I tried to create this class:
package com.wayfair.samworkgroupsservice.adapter
import io.r2dbc.mssql.MssqlConnectionConfiguration
import io.r2dbc.mssql.MssqlConnectionFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.config.ConstructorArgumentValues
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.GenericBeanDefinition
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Profile
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.springframework.data.r2dbc.dialect.SqlServerDialect
import org.springframework.r2dbc.core.DatabaseClient
import org.springframework.stereotype.Component
#Component
#Profile("test")
class TemplateFactory(
#Autowired val applicationContext: ApplicationContext
) {
private val beanFactory = applicationContext.autowireCapableBeanFactory as BeanDefinitionRegistry
fun registerTemplateBean(host: String, port: Int) {
val beanDefinition = GenericBeanDefinition()
beanDefinition.beanClass = R2dbcEntityTemplate::class.java
val args = ConstructorArgumentValues()
args.addIndexedArgumentValue(
0,
DatabaseClient.builder()
.connectionFactory(connectionFactory(host, port))
.bindMarkers(SqlServerDialect.INSTANCE.bindMarkersFactory)
.build()
)
args.addIndexedArgumentValue(1, DefaultReactiveDataAccessStrategy(SqlServerDialect.INSTANCE))
beanDefinition.constructorArgumentValues = args
beanFactory.registerBeanDefinition("R2dbcEntityTemplate", beanDefinition)
}
// fun entityTemplate(host: String = "localhost", port: Int = 1435) =
// R2dbcEntityTemplate(
// DatabaseClient.builder()
// .connectionFactory(connectionFactory(host, port))
// .bindMarkers(SqlServerDialect.INSTANCE.bindMarkersFactory)
// .build(),
// DefaultReactiveDataAccessStrategy(SqlServerDialect.INSTANCE)
// )
private fun connectionFactory(host: String, port: Int) =
MssqlConnectionFactory(
MssqlConnectionConfiguration.builder()
.host(host)
.port(port)
.username("sa")
.password("Password123##?")
.build()
)
}
And this is how my db initiliser looks like:
package com.wayfair.samworkgroupsservice.adapter.note
import com.wayfair.samworkgroupsservice.adapter.DBInitializerInterface
import com.wayfair.samworkgroupsservice.adapter.TemplateFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.testcontainers.containers.DockerComposeContainer
import org.testcontainers.containers.wait.strategy.Wait
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.io.File
#Testcontainers
class NoteTagDBInitializer : DBInitializerInterface {
#Autowired
override lateinit var client: R2dbcEntityTemplate
#Autowired
lateinit var factory: TemplateFactory
override val sqlScripts = listOf(
"db/note/schema.sql",
"db/note/reset.sql",
"db/note/data.sql"
)
init {
factory.registerTemplateBean(
cont.getServiceHost("test-db-local_1", 1433),
cont.getServicePort("test-db-local_1", 1433)
)
}
companion object {
#Container
val cont: KDockerComposerContainer = KDockerComposerContainer("docker-compose.test.yml")
.withExposedService(
"test-db-local_1", 1433,
Wait.forListeningPort()
)
.withLocalCompose(true)
.also {
it.start()
val porttt = it.getServicePort("test-db-local_1", 1433)
print(porttt)
}
class KDockerComposerContainer(yamlFile: String) :
DockerComposeContainer<KDockerComposerContainer>(File(yamlFile))
}
}
I am not getting errors when trying to start this template factory with no useful error message,
But to be honest I don't know anymore if am putting effort into the correct solution, does anyone have any insight on how to pull this off or if I am doing anything wrong here?
So to summarise for production app it is fine, it starts based off of the url on application.yaml file and that's it, but for tests I need something dinamic with ports that will change everytime.
Thank you in advance ))
Spring already has a solution for your problem.
If you're using a quite recent Spring version (>= 5.2.5), you should utilize #DynamicPropertySource in order to adjust your test configuration properties with a dynamic value of the container database port. Read official spring documentation for more details and kotlin code examples.
If you're stuck with an older Spring version, the interface you need is ApplicationContextInitializer. See this spring github issue for a small example.

XDocReport converting odt to pdf how to set proper locale

I'm trying to convert some *.odt file to *.pdf using IXDocReport.
Here is the hypothetical content of the *.odt file: ${amount?string.currency} to be paid
Here is the code I do conversion with (you can run it in kotlin REPL):
import fr.opensagres.xdocreport.converter.ConverterTypeTo
import fr.opensagres.xdocreport.converter.ConverterTypeVia
import fr.opensagres.xdocreport.converter.Options
import fr.opensagres.xdocreport.document.IXDocReport
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry
import fr.opensagres.xdocreport.template.TemplateEngineKind
import java.io.ByteArrayInputStream
import java.io.File
val options: Options = Options.getTo(ConverterTypeTo.PDF).via(ConverterTypeVia.ODFDOM)
val content: ByteArray = File("/home/sandro/tmp/report.odt").readBytes()
val templateId: String = "someId"
val registry: XDocReportRegistry = XDocReportRegistry.getRegistry()
val data: MutableMap<String, Any> = mutableMapOf("amount" to 10)
ByteArrayInputStream(content).use { input ->
val report: IXDocReport =
registry.loadReport(input, templateId, TemplateEngineKind.Freemarker, true)
val tmpFile: File = createTempFile("out", ".pdf")
tmpFile.outputStream().use { output ->
report.convert(data, options, output)
println(tmpFile.toString())
}
}
and the result is the pdf file with string $10.00 to be paid
How can I set needed locale to XDocReport during conversion so the result could be changed to other currencies correctly?
P.S. I cannot control the template itself - so please do not tell me to add <#setting locale="${bean.locale}"> or something else to the template itself. The only place I can change is the code. Thanks in advance.
P.P.S. I need to render many templates per request and need to set locale per each template.
I have never used XDocReport, but maybe this will work: https://github.com/opensagres/xdocreport/wiki/FreemarkerTemplate "How to configure Freemarker?"
Quotation from there:
To configure Freemarker with XDocReport you must get the Configuration instance. To do > that you must
create a class (ex :
fr.opensagres.xdocreport.MyFreemarkerConfiguration) which implements
fr.opensagres.xdocreport.document.discovery.ITemplateEngineInitializerDiscovery.
register with SPI this class by creating the file
META-INF/services/fr.opensagres.xdocreport.document.discovery.ITemplateEngineInitializerDiscovery
with the name of you class :
fr.opensagres.xdocreport.MyFreemarkerConfiguration This file should be
in your classpath (you can for instance host it in the
src/META-INF/services/ of your project).
So you will need a class like this:
public class MyFreemarkerConfiguration implements ITemplateEngineInitializerDiscovery {
[...]
public void initialize(ITemplateEngine templateEngine) {
if (TemplateEngineKind.Freemarker.name().equals( templateEngine.getKind())) {
Configuration cfg = ((FreemarkerTemplateEngine) templateEngine).getFreemarkerConfiguration();
cfg.setLocale(...);
}
}
}

Running Kotlin HTML Builder in the Browser

I am a Java developer that is very new to Kotlin. I love the language though, and I like how easily web applications can be done with it. The problem is that I cannot figure out how to run Kotlin HTML builder files in the browser, so that I can create a basic web page skeleton in kotlin. I can output it in the IDE, but it is silly how hard it seems to be to get it to run in my browser. This may be a dumb question and I'm missing something very obvious, but I can't seem to find the answer online.
Keep in mind that I'm not using the Intelli-J IDE. Would love to, but can't afford to pay out the nose just to do web development in Kotlin. Been using Eclipse.
Thanks in advance.
When you use Kotlin html builders kotlinx.html or any other of that sort, you need to, well, build them in order to get HTML for the browser.
There are no such thing as "Kotlin builder files". Those constructs are plain Kotlin code, so you write them inside your (server?) codebase, compile them and then invoke them to generate HTML responses. This also means you need a (normal Java) router framework, like Spark for example.
To sum up, html-builders are a way to generate HTML strings, so they do not include a way to ship the HTML elsewhere.
Kotlinx itself doesn't have any utilities to send the result to the user's browser. It's just a regular Kotlin code which can create HTML string. You need a way to send it to the user. There are some.
The simplest one is plain old Java servlets. Anybody still using them?
#WebServlet(urlPatterns = arrayOf("/servlet"), loadOnStartup = 1)
class KotlinxHtmlServlet : HttpServlet() {
override fun doGet(request: HttpServletRequest?, response: HttpServletResponse?) {
response!!.setContentType("text/html")
response!!.writer.appendHTML(true).html {
head {
title = "Hello from kotlinx.html + Servlets"
}
body {
h1 { +"Kotlin is awesome" }
p {
+"Read more about "
a("http://kotlinlang.org") {
target = ATarget.blank
+"it"
}
}
}
}
}
}
Spring Boot is very popular today. However, this #Controller will work in vanilla Spring too:
#Controller
class KotlinxHtmlController {
#ResponseBody
#RequestMapping(path = arrayOf("controller"), method = arrayOf(RequestMethod.GET))
fun doGet(): String {
return createHTML(true).html {
head {
title = "Hello from kotlinx.html + Servlets"
}
body {
h1 { +"Kotlin is awesome" }
p {
+"Read more about "
a("http://kotlinlang.org") {
target = ATarget.blank
+"it"
}
}
}
}
}
}
SparkJava is one of the plenty of young Java micro-frameworks. Note, that in case of SparkJava you can just write routes inside your main:
fun main(args: Array<String>): Unit {
get("spark", { request: Request, response: Response ->
createHTML(true).html {
head {
title = "Hello from kotlinx.html + Servlets"
}
body {
h1 { +"Kotlin is awesome" }
p {
+"Read more about "
a("http://kotlinlang.org") {
target = ATarget.blank
+"it"
}
}
}
}
})
}
I'm leaving dependency management, running the app and guessing the correct URLs to access generated pages to you. All of the above examples will result in this HTML:
<html>
<head title="Hello from kotlinx.html + Servlets"></head>
<body>
<h1>Kotlin is awesome</h1>
<p>Read more about it</p>
</body>
</html>
You can also try Dropwizard or Ninja frameworks.
Also, you can take a look at Kara – a web frameworks especially designed for Kotlin – but it is still in alpha stage.
I may be missing something here, but if using kotlinx.html javascript version, the resultant js code does perform as a DOM builder ... can add more if this is what is required.

Sonar Custom Widget

I created a widget using the source code available in github. Now I'm using that widget in SonarQube V5.3. This is where I got the source code from:
https://github.com/SonarSource/sonar-examples/tree/master/plugins/sonar-reference-plugin
When I use this widget it is showing up the same data across multiple projects. I would like to know if there is any way I can display different data for different projects. Please share your ideas. Below is the code that displays the ruby widget
import org.sonar.api.web.AbstractRubyTemplate;
import org.sonar.api.web.Description;
import org.sonar.api.web.RubyRailsWidget;
import org.sonar.api.web.UserRole;
import org.sonar.api.web.WidgetCategory;
import org.sonar.api.web.WidgetProperties;
import org.sonar.api.web.WidgetProperty;
import org.sonar.api.web.WidgetPropertyType;
import org.sonar.api.batch.CheckProject;
import org.sonar.api.resources.Project;
#UserRole(UserRole.USER)
#Description("Sample")
#WidgetCategory("Sample")
#WidgetProperties({
#WidgetProperty(key = "Index",type=WidgetPropertyType.TEXT
),
})
public class OneMoreRubyWidget extends AbstractRubyTemplate implements RubyRailsWidget {
#Override
public String getId() {
return "Sample";
}
#Override
public String getTitle() {
return "Sample";
}
#Override
protected String getTemplatePath() {
return "/example/Index.html.erb";
}
}
Thank you so much in advance
You haven't specified global scope for your widget (#WidgetScope("GLOBAL")) in the .java file, so this is a question of what's in your .erb file.
This Widget Lab property widget should give you some pointers. Specifically: you want to pick up #project in your widget, and query with #project.uuid. Here's another project-level widget for comparison.
You should be aware, though, that SonarSource is actively working to remove Ruby from the platform, so at some future date, you'll probably end up re-writing your widgets (likely in pure JavaScript).

Geb page url method from ConfigSlurper

I am trying to store the urls I need in a config file that gets pulled using ConfigSlurper. I think this may not work but not sure. Thoughts?
You are probably looking for functionality provided by baseUrl configuration. When using to MyPage the url which is used by the browser is determined by combining basUrl configuration and the url property of your page class.
If you wanted a slightly cleaner method of doing this, you could implement a base page such as the one below - inner class for brevity and to avoid calling protected methods directly - (we have apps on 26 different subdomains!):
package page.admin
import geb.Configuration
import geb.Page
class AdminPage extends Page {
class WrappedConfig extends Configuration {
WrappedConfig(ConfigObject rawConfig) {
super(rawConfig)
}
String getAdminBaseUrl() {
return readValue('adminUrl', '<invalid-url>')
}
}
String getPageUrl() {
WrappedConfig config = new WrappedConfig(browser.config.rawConfig)
return config.adminBaseUrl + this.class.url
}
}
Your config might look something like this:
baseUrl = 'http://base-app.example.net'
adminUrl = 'http://admin-app.example.com'
This way, you can still use normal geb syntax:
given:
to PageWhichExtendsAdminPage, 'your-path', key1: 'value1
to generate the url http://admin-app.example.com/your-path/?key1=value1
I run geb on different locales so I encountered the same issue. I usually load the different urls out of a config file with locale.getCountry() as parameter for the environment.
In the running class I replace the baseUrl with the loaded entry with the ConfigSlurper. The advantage is that I can handle multiple locales and localhost environments. Testing locally vs testing the staging environment.
I have one main spock file containing the whole regression testing and a inheriting spock class for every country. The inheriting spock files doesn't contain much except the country/language encoding in the class name.
The config file:
environment{
CA{
url="ca.somewhere.com"
validZipCode=...
...
}
...
}
The main class:
class MainRegression extends GebReportingSpec{
#Shared Locale locale
def setupSpec(){
...
locale = ParameterReader.getLocale(this.class.name)
...
}
def "testing the website"(){
when: "entering the main url"
go URLService.getBaseUrl(locale)
...
}
The inheriting class:
#Stepwise
class CaEnRegressionSpec{} // Canada with english language
A good way to handle the at-verification with different languages / locales:
http://ldaley.com/post/1013531080/painless-page-identification-with-geb-grails