mattermost-plugin-shelfmark/server/i18n.go
Felipe M. a4ee9379eb
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
ci / plugin-ci (push) Has been cancelled
Validate channel existence and Shelfmark reachability on config change
Add early validation in OnConfigurationChange to verify the configured
channel exists in Mattermost and the Shelfmark server is reachable,
giving admins immediate feedback instead of discovering issues at runtime.

- Add Ping() method to shelfmark.Client for lightweight reachability check
- Add validateConfiguration() on Plugin that checks channel + Shelfmark
- Add i18n messages for channel not found and Shelfmark unreachable
- Add unit tests for Ping and validateConfiguration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:55:08 +01:00

127 lines
6 KiB
Go

package main
import (
"fmt"
"strings"
)
// Message key constants for i18n.
const (
MsgCommandEmpty = "command.empty"
MsgCommandUnknown = "command.unknown"
MsgNotConfigured = "requestbook.not_configured"
MsgUsage = "requestbook.usage"
MsgSearchFailed = "requestbook.search_failed"
MsgNoBooksFound = "requestbook.no_books_found"
MsgQueueFailed = "requestbook.queue_failed"
MsgBookFound = "requestbook.book_found"
MsgLanguageInfo = "requestbook.language_info"
MsgDownloadTimeout = "job.download_timeout"
MsgNoReleases = "job.no_releases"
MsgNoReleasesFound = "job.no_releases_found"
MsgDownloadStartFailed = "job.download_start_failed"
MsgShelfmarkError = "job.shelfmark_error"
MsgTrackingLost = "job.tracking_lost"
MsgDownloadStatus = "job.download_status"
MsgDownloadFailed = "job.download_failed"
MsgPostFailed = "job.post_failed"
MsgUploadFailed = "job.upload_failed"
MsgAttachFailed = "job.attach_failed"
MsgTeamNotAllowed = "command.team_not_allowed"
MsgChannelNotFound = "config.channel_not_found"
MsgShelfmarkUnreachable = "config.shelfmark_unreachable"
)
// messages maps locale → key → format string.
var messages = map[string]map[string]string{
"en": {
MsgCommandEmpty: "Empty command received.",
MsgCommandUnknown: "Unknown command: %s",
MsgNotConfigured: "Plugin is not configured: %s",
MsgUsage: "Usage: `/requestbook [--language <code>] <search query>`\nExample: `/requestbook The Hitchhiker's Guide to the Galaxy`\nExample: `/requestbook --language es Don Quixote`",
MsgSearchFailed: "Failed to search for books. Please check the plugin configuration and try again.",
MsgNoBooksFound: "No books found for \"%s\".",
MsgQueueFailed: "Failed to queue the book request. Please try again.",
MsgBookFound: "Book \"%s\" found%s. The download has been queued and will be posted when ready.",
MsgLanguageInfo: " (language: %s)",
MsgDownloadTimeout: "Download for \"%s\" timed out. Please try again.",
MsgNoReleases: "Could not find any downloadable files for \"%s\".",
MsgNoReleasesFound: "No downloadable files were found for \"%s\".",
MsgDownloadStartFailed: "Failed to start the download for \"%s\". Please try again.",
MsgShelfmarkError: "Shelfmark could not process the download for \"%s\".",
MsgTrackingLost: "The download for \"%s\" could not be tracked on Shelfmark.",
MsgDownloadStatus: "The download for \"%s\" has %s on Shelfmark.",
MsgDownloadFailed: "Failed to download \"%s\" from Shelfmark.",
MsgPostFailed: "Failed to create the book post for \"%s\".",
MsgUploadFailed: "Failed to upload the file for \"%s\".",
MsgAttachFailed: "Failed to attach the file for \"%s\".",
MsgTeamNotAllowed: "This plugin is not available on this team.",
MsgChannelNotFound: "The configured channel ID does not exist.",
MsgShelfmarkUnreachable: "Cannot reach the Shelfmark server.",
},
"es": {
MsgCommandEmpty: "Comando vacío recibido.",
MsgCommandUnknown: "Comando desconocido: %s",
MsgNotConfigured: "El plugin no está configurado: %s",
MsgUsage: "Uso: `/requestbook [--language <código>] <búsqueda>`\nEjemplo: `/requestbook The Hitchhiker's Guide to the Galaxy`\nEjemplo: `/requestbook --language es Don Quixote`",
MsgSearchFailed: "Error al buscar libros. Por favor, compruebe la configuración del plugin e inténtelo de nuevo.",
MsgNoBooksFound: "No se encontraron libros para \"%s\".",
MsgQueueFailed: "Error al encolar la solicitud del libro. Por favor, inténtelo de nuevo.",
MsgBookFound: "Libro \"%s\" encontrado%s. La descarga ha sido añadida a la cola y se publicará cuando esté lista.",
MsgLanguageInfo: " (idioma: %s)",
MsgDownloadTimeout: "La descarga de \"%s\" ha expirado. Por favor, inténtelo de nuevo.",
MsgNoReleases: "No se encontraron archivos descargables para \"%s\".",
MsgNoReleasesFound: "No se encontraron archivos descargables para \"%s\".",
MsgDownloadStartFailed: "Error al iniciar la descarga de \"%s\". Por favor, inténtelo de nuevo.",
MsgShelfmarkError: "Shelfmark no pudo procesar la descarga de \"%s\".",
MsgTrackingLost: "No se pudo rastrear la descarga de \"%s\" en Shelfmark.",
MsgDownloadStatus: "La descarga de \"%s\" tiene estado %s en Shelfmark.",
MsgDownloadFailed: "Error al descargar \"%s\" desde Shelfmark.",
MsgPostFailed: "Error al crear la publicación del libro \"%s\".",
MsgUploadFailed: "Error al subir el archivo de \"%s\".",
MsgAttachFailed: "Error al adjuntar el archivo de \"%s\".",
MsgTeamNotAllowed: "Este plugin no está disponible en este equipo.",
MsgChannelNotFound: "El canal configurado no existe.",
MsgShelfmarkUnreachable: "No se puede conectar con el servidor Shelfmark.",
},
}
// T returns a localized, formatted message. Falls back to English if the locale
// or key is not found.
func T(locale, key string, args ...any) string {
format := localizedMessage(locale, key)
if len(args) == 0 {
return format
}
return fmt.Sprintf(format, args...)
}
// localizedMessage resolves the format string for a given locale and key.
// It normalizes locales like "es-ES" → "es" and falls back to "en".
func localizedMessage(locale, key string) string {
normalized := normalizeLocale(locale)
if msgs, ok := messages[normalized]; ok {
if format, ok := msgs[key]; ok {
return format
}
}
// Fallback to English.
if msgs, ok := messages["en"]; ok {
if format, ok := msgs[key]; ok {
return format
}
}
return key
}
// normalizeLocale extracts the base language from a locale string (e.g., "es-ES" → "es").
func normalizeLocale(locale string) string {
if locale == "" {
return "en"
}
parts := strings.SplitN(locale, "-", 2)
return strings.ToLower(parts[0])
}