feat: add i18n support to web templates

- Add i18n object to template funcMap for direct access
  - Translate all hardcoded strings in CSAT and footer templates
  - Add reusable translation keys to globals.messages
This commit is contained in:
Abhinav Raut
2025-08-30 19:35:00 +05:30
parent f6d3bd543f
commit a516773b14
6 changed files with 39 additions and 24 deletions

View File

@@ -17,7 +17,7 @@ func handleShowCSAT(r *fastglue.Request) error {
if err != nil {
return app.tmpl.RenderWebPage(r.RequestCtx, "error", map[string]interface{}{
"Data": map[string]interface{}{
"ErrorMessage": "Page not found",
"ErrorMessage": app.i18n.T("globals.messages.pageNotFound"),
},
})
}
@@ -25,8 +25,8 @@ func handleShowCSAT(r *fastglue.Request) error {
if csat.ResponseTimestamp.Valid {
return app.tmpl.RenderWebPage(r.RequestCtx, "info", map[string]interface{}{
"Data": map[string]interface{}{
"Title": "Thank you!",
"Message": "We appreciate you taking the time to submit your feedback.",
"Title": app.i18n.T("globals.messages.thankYou"),
"Message": app.i18n.T("csat.thankYouMessage"),
},
})
}
@@ -35,14 +35,14 @@ func handleShowCSAT(r *fastglue.Request) error {
if err != nil {
return app.tmpl.RenderWebPage(r.RequestCtx, "error", map[string]interface{}{
"Data": map[string]interface{}{
"ErrorMessage": "Page not found",
"ErrorMessage": app.i18n.T("globals.messages.pageNotFound"),
},
})
}
return app.tmpl.RenderWebPage(r.RequestCtx, "csat", map[string]interface{}{
"Data": map[string]interface{}{
"Title": "Rate your interaction with us",
"Title": app.i18n.T("csat.pageTitle"),
"CSAT": map[string]interface{}{
"UUID": csat.UUID,
},
@@ -67,7 +67,7 @@ func handleUpdateCSATResponse(r *fastglue.Request) error {
if err != nil {
return app.tmpl.RenderWebPage(r.RequestCtx, "error", map[string]interface{}{
"Data": map[string]interface{}{
"ErrorMessage": "Invalid `rating`",
"ErrorMessage": app.i18n.T("globals.messages.somethingWentWrong"),
},
})
}
@@ -75,7 +75,7 @@ func handleUpdateCSATResponse(r *fastglue.Request) error {
if ratingI < 1 || ratingI > 5 {
return app.tmpl.RenderWebPage(r.RequestCtx, "error", map[string]interface{}{
"Data": map[string]interface{}{
"ErrorMessage": "Invalid `rating`",
"ErrorMessage": app.i18n.T("globals.messages.somethingWentWrong"),
},
})
}
@@ -83,7 +83,7 @@ func handleUpdateCSATResponse(r *fastglue.Request) error {
if uuid == "" {
return app.tmpl.RenderWebPage(r.RequestCtx, "error", map[string]interface{}{
"Data": map[string]interface{}{
"ErrorMessage": "Invalid `uuid`",
"ErrorMessage": app.i18n.T("globals.messages.somethingWentWrong"),
},
})
}
@@ -98,8 +98,8 @@ func handleUpdateCSATResponse(r *fastglue.Request) error {
return app.tmpl.RenderWebPage(r.RequestCtx, "info", map[string]interface{}{
"Data": map[string]interface{}{
"Title": "Thank you!",
"Message": "We appreciate you taking the time to submit your feedback.",
"Title": app.i18n.T("csat.thankYou"),
"Message": app.i18n.T("csat.thankYouMessage"),
},
})
}

View File

@@ -329,7 +329,7 @@ func initWS(user *user.Manager) *ws.Hub {
func initTemplate(db *sqlx.DB, fs stuffbin.FileSystem, consts *constants, i18n *i18n.I18n) *tmpl.Manager {
var (
lo = initLogger("template")
funcMap = getTmplFuncs(consts)
funcMap = getTmplFuncs(consts, i18n)
)
tpls, err := stuffbin.ParseTemplatesGlob(funcMap, fs, "/static/email-templates/*.html")
if err != nil {
@@ -347,7 +347,7 @@ func initTemplate(db *sqlx.DB, fs stuffbin.FileSystem, consts *constants, i18n *
}
// getTmplFuncs returns the template functions.
func getTmplFuncs(consts *constants) template.FuncMap {
func getTmplFuncs(consts *constants, i18n *i18n.I18n) template.FuncMap {
return template.FuncMap{
"RootURL": func() string {
return consts.AppBaseURL
@@ -367,6 +367,7 @@ func getTmplFuncs(consts *constants) template.FuncMap {
"SiteName": func() string {
return consts.SiteName
},
"i18n": i18n,
}
}
@@ -395,7 +396,7 @@ func reloadSettings(app *App) error {
// reloadTemplates reloads the templates from the filesystem.
func reloadTemplates(app *App) error {
app.lo.Info("reloading templates")
funcMap := getTmplFuncs(app.consts.Load().(*constants))
funcMap := getTmplFuncs(app.consts.Load().(*constants), app.i18n)
tpls, err := stuffbin.ParseTemplatesGlob(funcMap, app.fs, "/static/email-templates/*.html")
if err != nil {
app.lo.Error("error parsing email templates", "error", err)

View File

@@ -307,6 +307,12 @@
"globals.messages.reset": "Reset {name}",
"globals.messages.lastNItems": "Last {n} {name} | Last {n} {name}",
"globals.messages.correctEmailErrors": "Please correct the email errors",
"globals.messages.additionalFeedback": "Additional feedback (optional)",
"globals.messages.pleaseSelect": "Please select {name} before submitting",
"globals.messages.poweredBy": "Powered by",
"globals.messages.thankYou": "Thank you!",
"globals.messages.pageNotFound": "Page not found",
"globals.messages.somethingWentWrong": "Something went wrong",
"form.error.min": "Must be at least {min} characters",
"form.error.max": "Must be at most {max} characters",
"form.error.minmax": "Must be between {min} and {max} characters",
@@ -340,6 +346,14 @@
"conversationStatus.alreadyInUse": "Cannot delete status as it is in use, Please remove this status from all conversations before deleting",
"conversationStatus.cannotUpdateDefault": "Cannot update default conversation status",
"csat.alreadySubmitted": "CSAT already submitted",
"csat.rateYourInteraction": "Rate your recent interaction",
"csat.rating.poor": "Poor",
"csat.rating.fair": "Fair",
"csat.rating.good": "Good",
"csat.rating.great": "Great",
"csat.rating.excellent": "Excellent",
"csat.pageTitle": "Rate your interaction with us",
"csat.thankYouMessage": "We appreciate you taking the time to submit your feedback.",
"auth.csrfTokenMismatch": "CSRF token mismatch",
"auth.invalidOrExpiredSession": "Invalid or expired session",
"auth.invalidOrExpiredSessionClearCookie": "Invalid or expired session. Please clear your cookies and try again.",

View File

@@ -5,7 +5,7 @@
{{ if ne SiteName "" }}
Welcome to {{ SiteName }}
{{ else }}
Welcome
Welcome to Libredesk
{{ end }}
</h1>

View File

@@ -2,7 +2,7 @@
{{ template "header" . }}
<div class="csat-container">
<div class="csat-header">
<h2>Rate your recent interaction</h2>
<h2>{{ i18n.T "csat.rateYourInteraction" }}</h2>
{{ if .Data.Conversation.Subject }}
<p class="conversation-subject"><i>{{ .Data.Conversation.Subject }}</i></p>
{{ end }}
@@ -16,7 +16,7 @@
<div class="emoji-wrapper">
<span class="emoji">😢</span>
</div>
<span class="rating-text">Poor</span>
<span class="rating-text">{{ i18n.T "csat.rating.poor" }}</span>
</label>
<input type="radio" id="rating-2" name="rating" value="2">
@@ -24,7 +24,7 @@
<div class="emoji-wrapper">
<span class="emoji">😕</span>
</div>
<span class="rating-text">Fair</span>
<span class="rating-text">{{ i18n.T "csat.rating.fair" }}</span>
</label>
<input type="radio" id="rating-3" name="rating" value="3">
@@ -32,7 +32,7 @@
<div class="emoji-wrapper">
<span class="emoji">😊</span>
</div>
<span class="rating-text">Good</span>
<span class="rating-text">{{ i18n.T "csat.rating.good" }}</span>
</label>
<input type="radio" id="rating-4" name="rating" value="4">
@@ -40,7 +40,7 @@
<div class="emoji-wrapper">
<span class="emoji">😃</span>
</div>
<span class="rating-text">Great</span>
<span class="rating-text">{{ i18n.T "csat.rating.great" }}</span>
</label>
<input type="radio" id="rating-5" name="rating" value="5">
@@ -48,18 +48,18 @@
<div class="emoji-wrapper">
<span class="emoji">🤩</span>
</div>
<span class="rating-text">Excellent</span>
<span class="rating-text">{{ i18n.T "csat.rating.excellent" }}</span>
</label>
</div>
<!-- Validation message for rating -->
<div class="validation-message" id="ratingValidationMessage"
style="display: none; color: #dc2626; text-align: center; margin-top: 10px; font-size: 0.9em;">
Please select a rating before submitting.
{{ i18n.Ts "globals.messages.pleaseSelect" "name" "rating" }}
</div>
</div>
<div class="feedback-container">
<label for="feedback" class="feedback-label">Additional feedback (optional)</label>
<label for="feedback" class="feedback-label">{{ i18n.T "globals.messages.additionalFeedback" }}</label>
<textarea id="feedback" name="feedback" placeholder="" rows="6" maxlength="1000"
onkeyup="updateCharCount(this)"></textarea>
<div class="char-counter">
@@ -67,7 +67,7 @@
</div>
</div>
<button type="submit" class="button submit-button">Submit</button>
<button type="submit" class="button submit-button">{{ i18n.T "globals.messages.submit" }}</button>
</form>
</div>

View File

@@ -31,7 +31,7 @@
{{ define "footer" }}
</div>
<footer class="container">
Powered by <a target="_blank" rel="noreferrer" href="https://libredesk.io/">Libredesk</a>
{{ i18n.T "globals.messages.poweredBy" }} <a target="_blank" rel="noreferrer" href="https://libredesk.io/">Libredesk</a>
</footer>
</body>
</html>