Fix view webapp translations

This commit is contained in:
Claude Brisson
2023-06-18 15:47:33 +02:00
parent c2a91a7b37
commit 731d798e24
6 changed files with 87 additions and 24 deletions

View File

@@ -103,6 +103,7 @@
</httpConnector> </httpConnector>
<systemProperties> <systemProperties>
<pairgoth.api.url>http://localhost:8085/api/</pairgoth.api.url> <pairgoth.api.url>http://localhost:8085/api/</pairgoth.api.url>
<pairgoth.env>dev</pairgoth.env>
</systemProperties> </systemProperties>
<webApp> <webApp>
<resourceBases>${project.basedir}/src/main/webapp,${project.build.directory}/generated-resources/</resourceBases> <resourceBases>${project.basedir}/src/main/webapp,${project.build.directory}/generated-resources/</resourceBases>

View File

@@ -7,12 +7,14 @@ import org.apache.velocity.runtime.parser.node.ASTText
import org.apache.velocity.runtime.parser.node.SimpleNode import org.apache.velocity.runtime.parser.node.SimpleNode
import org.jeudego.pairgoth.web.WebappManager import org.jeudego.pairgoth.web.WebappManager
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.Path import java.nio.file.Path
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentSkipListSet
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.io.path.readLines import kotlin.io.path.readLines
import kotlin.io.path.useDirectoryEntries import kotlin.io.path.useDirectoryEntries
@@ -79,7 +81,7 @@ class Translator private constructor(private val iso: String) {
if (groupStart > start) output.print(text.substring(start, groupStart)) if (groupStart > start) output.print(text.substring(start, groupStart))
val capture = matcher.group(group) val capture = matcher.group(group)
var token: String = StringEscapeUtils.unescapeHtml4(capture) var token: String = StringEscapeUtils.unescapeHtml4(capture)
if (StringUtils.containsOnly(token, "\r\n\t -;:.\"/<>\u00A00123456789€!")) output.print(capture) else { if (StringUtils.containsOnly(token, "\r\n\t -;:.'\"/<>\u00A00123456789€[]!")) output.print(capture) else {
token = normalize(token) token = normalize(token)
token = translate(token) token = translate(token)
output.print(StringEscapeUtils.escapeHtml4(token)) output.print(StringEscapeUtils.escapeHtml4(token))
@@ -104,17 +106,15 @@ class Translator private constructor(private val iso: String) {
map[0] = (ignoring != null) map[0] = (ignoring != null)
while (pos < text.length) { while (pos < text.length) {
if (ignoring == null) { if (ignoring == null) {
val nextIgnore = ignoredTags.map { tag -> val nextIgnore = ignoredTags.mapNotNull { tag ->
Pair(tag, text.indexOf("<$tag(?:>\\s)")) Regex("<($tag)(?:>|\\s)").find(text)
}.filter { }.minByOrNull {
it.second != -1 it.range.first
}.sortedBy { }
it.second
}.firstOrNull()
if (nextIgnore == null) pos = text.length if (nextIgnore == null) pos = text.length
else { else {
ignoring = nextIgnore.first ignoring = nextIgnore.groupValues[1]
pos += nextIgnore.first.length + 2 pos += ignoring.length + 2
} }
} else { } else {
val closingTag = text.indexOf("</$ignoring>") val closingTag = text.indexOf("</$ignoring>")
@@ -132,9 +132,12 @@ class Translator private constructor(private val iso: String) {
get() = textAccessor[this] as String get() = textAccessor[this] as String
set(value: String) { textAccessor[this] = value } set(value: String) { textAccessor[this] = value }
private val saveMissingTranslations = System.getProperty("pairgoth.env") == "dev"
private val missingTranslations: MutableSet<String> = ConcurrentSkipListSet()
private fun reportMissingTranslation(enText: String) { private fun reportMissingTranslation(enText: String) {
logger.warn("missing translation towards {}: {}", iso, enText) logger.warn("missing translation towards {}: {}", iso, enText)
// CB TODO - create file if (saveMissingTranslations) missingTranslations.add(enText)
} }
companion object { companion object {
@@ -165,5 +168,18 @@ class Translator private constructor(private val iso: String) {
val providedLanguages = setOf("en", "fr") val providedLanguages = setOf("en", "fr")
const val defaultLanguage = "en" const val defaultLanguage = "en"
internal fun notifyExiting() {
translators.values.filter {
it.saveMissingTranslations && it.missingTranslations.isNotEmpty()
}.forEach {
val missing = File("${it.iso}.missing")
logger.info("Saving missing translations for ${it.iso} to ${missing.canonicalPath}")
missing.printWriter().use { out ->
out.println(it.missingTranslations.map { "${it}\t" }.joinToString("\n"))
}
}
}
} }
} }

View File

@@ -2,6 +2,7 @@ package org.jeudego.pairgoth.web
import com.republicate.mailer.SmtpLoop import com.republicate.mailer.SmtpLoop
import org.apache.commons.lang3.tuple.Pair import org.apache.commons.lang3.tuple.Pair
import org.jeudego.pairgoth.util.Translator
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.IOException import java.io.IOException
import java.lang.IllegalAccessError import java.lang.IllegalAccessError
@@ -51,7 +52,7 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
/* ServletContextListener interface */ /* ServletContextListener interface */
override fun contextInitialized(sce: ServletContextEvent) { override fun contextInitialized(sce: ServletContextEvent) {
context = sce.servletContext context = sce.servletContext
logger.info("---------- Starting Pairgoth Web Client ----------") logger.info("---------- Starting $WEBAPP_NAME ----------")
context.setAttribute("manager", this) context.setAttribute("manager", this)
webappRoot = context.getRealPath("/") webappRoot = context.getRealPath("/")
try { try {
@@ -78,11 +79,15 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
} }
override fun contextDestroyed(sce: ServletContextEvent) { override fun contextDestroyed(sce: ServletContextEvent) {
logger.info("---------- Stopping Web Application ----------") logger.info("---------- Stopping $WEBAPP_NAME ----------")
Translator.notifyExiting()
val context = sce.servletContext val context = sce.servletContext
for (service in webServices.keys) stopService(service, true) for (service in webServices.keys) stopService(service, true)
// ??? DriverManager.deregisterDriver(com.mysql.cj.jdbc.Driver ...); // ??? DriverManager.deregisterDriver(com.mysql.cj.jdbc.Driver ...);
logger.info("---------- Stopped $WEBAPP_NAME ----------")
} }
/* ServletContextAttributeListener interface */ /* ServletContextAttributeListener interface */
@@ -95,6 +100,7 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
override fun sessionDestroyed(se: HttpSessionEvent) {} override fun sessionDestroyed(se: HttpSessionEvent) {}
companion object { companion object {
const val WEBAPP_NAME = "Pairgoth Web Client"
const val PAIRGOTH_PROPERTIES_PREFIX = "pairgoth." const val PAIRGOTH_PROPERTIES_PREFIX = "pairgoth."
lateinit var webappRoot: String lateinit var webappRoot: String
lateinit var context: ServletContext lateinit var context: ServletContext

View File

@@ -1,2 +1,28 @@
New tournament Nouveau tournoi New tournament Nouveau tournoi
Open Ouvrir Open Ouvrir
Cancel Annuler
Infos Infos
Next Suivant
Pairing Appariement
Type Type
(pairgo / rengo) (pairgo / rengo)
(teams of individual players) (équipes de joueurs individuels)
MacMahon MacMahon
Number of rounds Nombre de rondes
Partner teams tournament of Tournoi d'équipes partenaires de
Standard tournament of individual players Tournoi standard de joueurs individuels
Swiss Suisse
Teams tournament of Tournoi par équipes de
Tournament dates Dates du tournoi
Tournament name Nom du tournoi
Tournament pairing Appariement du tournoi
Tournament short name Nom abbrégé du tournoi
Tournament type Type de tournoi
end date date de fin
from du
players joueurs
rounds rondes
short_name nom_tournoi
start date date de début
to au

View File

@@ -43,7 +43,7 @@
</div> </div>
</div> </div>
<div class="scrolling content"> <div class="scrolling content">
#parse('tournament-form.inc.html') #translate('tournament-form.inc.html')
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui cancel black floating button">Cancel</button> <button class="ui cancel black floating button">Cancel</button>

View File

@@ -1,20 +1,20 @@
<form id="new-tournament-form" class="ui form"> <form class="tournament-form" class="ui form">
<div class="ui infos tab segment"> <div class="ui infos tab segment">
<div class="field"> <div class="field">
<label>Tournament name</label> <label>Tournament name</label>
<input type="text" name="name" placeholder="Tournament name"/> <input type="text" name="name" required placeholder="Tournament name"/>
</div> </div>
<div class="field"> <div class="field">
<label>Tournament short name</label> <label>Tournament short name</label>
<input type="text" name="shortname" placeholder="short_name"/> <input type="text" name="shortname" required placeholder="short_name"/>
</div> </div>
<div class="field"> <div class="field">
<label>Tournament dates</label> <label>Tournament dates</label>
<span id="date-range">from <input type="text" name="start" class="date" placeholder="start date"/> to <input type="text" name="end" class="date" placeholder="end date"/></span> <span class="date-range">from <input type="text" name="start" required class="date" placeholder="start date"/> to <input type="text" name="end" required class="date" placeholder="end date"/></span>
</div> </div>
<div class="field"> <div class="field">
<label>Number of rounds</label> <label>Number of rounds</label>
<span><input type="number" name="rounds" min="1"/> rounds</span> <span><input type="number" name="rounds" required min="1"/> rounds</span>
</div> </div>
</div> </div>
<div class="ui type tab segment"> <div class="ui type tab segment">
@@ -23,7 +23,7 @@
<div class="field"> <div class="field">
<div class="ui radio"> <div class="ui radio">
<label> <label>
<input type="radio" name="type" value="standard"/> <input type="radio" name="type" value="standard" required/>
Standard tournament of individual players Standard tournament of individual players
</label> </label>
</div> </div>
@@ -31,7 +31,7 @@
<div class="field"> <div class="field">
<div class="ui radio"> <div class="ui radio">
<label> <label>
<input type="radio" name="type" value="partners"/> <input type="radio" name="type" value="partners" required/>
<span>Partner teams tournament of <input type="number" min="2" name="partners"/> players</span> (pairgo / rengo) <span>Partner teams tournament of <input type="number" min="2" name="partners"/> players</span> (pairgo / rengo)
</label> </label>
</div> </div>
@@ -39,7 +39,7 @@
<div class="field"> <div class="field">
<div class="ui radio"> <div class="ui radio">
<label> <label>
<input type="radio" name="type" value="teams"/> <input type="radio" name="type" value="teams" required/>
<span>Teams tournament of <input type="number" min="2" name="partners"/> players</span> (teams of individual players) <span>Teams tournament of <input type="number" min="2" name="partners"/> players</span> (teams of individual players)
</label> </label>
</div> </div>
@@ -52,7 +52,7 @@
<div class="field"> <div class="field">
<div class="ui radio"> <div class="ui radio">
<label> <label>
<input type="radio" name="pairing" value="macmahon"/> <input type="radio" name="pairing" value="macmahon" required/>
MacMahon MacMahon
</label> </label>
</div> </div>
@@ -60,7 +60,7 @@
<div class="field"> <div class="field">
<div class="ui radio"> <div class="ui radio">
<label> <label>
<input type="radio" name="pairing" value="swiss"/> <input type="radio" name="pairing" value="swiss" required/>
Swiss Swiss
</label> </label>
</div> </div>
@@ -68,3 +68,17 @@
</div> </div>
</div> </div>
</form> </form>
<script type="text/javascript">
onLoad(() => {
$('#tournament-form').on('input', e => {
})
});
</script>
CB TODO :
s'il y a duplication du formulaire (pour nouveau/édition), il faut changer les ids en class, gérer les pbs d'init du date-range, etc.
sinon, il faut paramétrer la dialog (mais les steps 1/2/3 deviennent des tabs, etc.)
Donc dans tous les cas il y a qqc à faire !