2023-06-29 17:19:12 +02:00
|
|
|
(defpackage cl-sbt
|
2023-07-01 16:30:10 +02:00
|
|
|
(:use :cl)
|
2023-07-14 16:20:55 +02:00
|
|
|
(:export
|
2023-08-25 15:10:01 +02:00
|
|
|
:write-html-to-file
|
2023-09-08 13:33:48 +02:00
|
|
|
:with-page
|
2023-09-13 21:23:19 +02:00
|
|
|
:l10n
|
2023-09-08 13:33:48 +02:00
|
|
|
:find-l10n))
|
2023-07-01 16:30:10 +02:00
|
|
|
|
2023-06-29 17:19:12 +02:00
|
|
|
(in-package :cl-sbt)
|
2023-07-03 14:43:58 +02:00
|
|
|
|
2023-07-22 13:58:46 +02:00
|
|
|
(setq spinneret:*fill-column* 120)
|
2023-07-20 08:32:44 +02:00
|
|
|
(defparameter *cdn-css* "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css")
|
|
|
|
(defparameter *cdn-js* "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js")
|
|
|
|
|
2023-09-22 14:44:25 +02:00
|
|
|
(defun dictp (lst)
|
|
|
|
(loop for entry in lst always
|
|
|
|
(and (listp entry)
|
|
|
|
(= (length entry) 2)
|
|
|
|
(stringp (first entry))
|
|
|
|
(mapcar (lambda (entry) (every #'stringp entry)) (rest entry)))))
|
|
|
|
|
|
|
|
(deftype dict ()
|
|
|
|
'(and list (satisfies dictp)))
|
|
|
|
|
|
|
|
(declaim (type dict l10n))
|
2023-09-13 21:23:19 +02:00
|
|
|
(defvar l10n '(("submit" ("en" "Submit" "de" "Absenden" "fr" "Soumettre"))
|
|
|
|
("cancel" ("en" "Cancel" "de" "Abbrechen" "fr" "Annuler"))
|
|
|
|
("upload" ("en" "Upload" "de" "Hochladen" "fr" "Télécharger"))
|
|
|
|
("search" ("en" "Search" "de" "Suchen" "fr" "Rechercher"))
|
|
|
|
("option-selected" ("en" "Open this selected menu"
|
|
|
|
"de" "Das ausgewählte Menü öffnen"
|
|
|
|
"fr" "Ouvrir le menu sélectionné"))
|
|
|
|
("sign-up" ("en" "Sign Up" "de" "Registrieren" "fr" "Inscrivez-vous"))
|
|
|
|
("sign-in" ("en" "Sign In" "de" "Anmelden" "fr" "S'identifier"))
|
|
|
|
("next" ("en" "Next" "de" "Weiter" "fr" "Suivant"))
|
|
|
|
("previous" ("en" "Previous" "de" "Zurück" "fr" "Précédent"))
|
|
|
|
("settings" ("en" "Settings" "de" "Einstellungen" "fr" "Paramètres"))
|
|
|
|
("logout" ("en" "Logout" "de" "Abmelden" "fr" "Déconnexion"))
|
|
|
|
("profile" ("en" "Profile" "de" "Profil" "fr" "Profil"))
|
|
|
|
("save" ("en" "Save" "de" "Speichern" "fr" "Enregistrer"))
|
|
|
|
("delete" ("en" "Delete" "de" "Löschen" "fr" "Supprimer"))
|
|
|
|
("edit" ("en" "Edit" "de" "Bearbeiten" "fr" "Modifier"))
|
|
|
|
("confirm" ("en" "Confirm" "de" "Bestätigen" "fr" "Confirmer"))
|
|
|
|
("loading" ("en" "Loading..." "de" "Lädt..." "fr" "Chargement..."))
|
|
|
|
("error" ("en" "Error" "de" "Fehler" "fr" "Erreur"))
|
|
|
|
("success" ("en" "Success" "de" "Erfolg" "fr" "Succès"))
|
|
|
|
("close" ("en" "Close" "de" "Schließen" "fr" "Fermer"))
|
2023-09-13 21:32:41 +02:00
|
|
|
("help" ("en" "Help" "de" "Hilfe" "fr" "Aide"))
|
|
|
|
("home" ("en" "Home" "de" "Startseite" "fr" "Accueil"))
|
|
|
|
("welcome" ("en" "Welcome" "de" "Willkommen" "fr" "Bienvenue"))
|
|
|
|
("faq" ("en" "FAQ" "de" "Häufig gestellte Fragen" "fr" "FAQ"))
|
|
|
|
("contact" ("en" "Contact" "de" "Kontakt" "fr" "Contact"))
|
|
|
|
("privacy" ("en" "Privacy" "de" "Datenschutz" "fr" "Confidentialité"))
|
|
|
|
("terms" ("en" "Terms and Conditions" "de" "Allgemeine Geschäftsbedingungen" "fr" "Conditions Générales"))
|
|
|
|
("about" ("en" "About Us" "de" "Über uns" "fr" "À propos de nous"))
|
|
|
|
("add-to-cart" ("en" "Add to Cart" "de" "In den Warenkorb" "fr" "Ajouter au panier"))
|
|
|
|
("checkout" ("en" "Checkout" "de" "Kasse" "fr" "Passer à la caisse"))
|
|
|
|
("forgot-password" ("en" "Forgot Password?" "de" "Passwort vergessen?" "fr" "Mot de passe oublié ?"))
|
|
|
|
("username" ("en" "Username" "de" "Benutzername" "fr" "Nom d'utilisateur"))
|
|
|
|
("password" ("en" "Password" "de" "Passwort" "fr" "Mot de passe"))
|
|
|
|
("email" ("en" "Email" "de" "E-Mail" "fr" "Courrier électronique"))
|
|
|
|
("language" ("en" "Language" "de" "Sprache" "fr" "Langue"))
|
|
|
|
("read-more" ("en" "Read More" "de" "Weiterlesen" "fr" "En savoir plus"))
|
|
|
|
("show-less" ("en" "Show Less" "de" "Weniger anzeigen" "fr" "Montrer moins"))
|
|
|
|
("update" ("en" "Update" "de" "Aktualisieren" "fr" "Mettre à jour"))
|
|
|
|
("new" ("en" "New" "de" "Neu" "fr" "Nouveau"))
|
|
|
|
("old" ("en" "Old" "de" "Alt" "fr" "Ancien"))
|
|
|
|
("view-all" ("en" "View All" "de" "Alle anzeigen" "fr" "Voir tout"))
|
|
|
|
("cart" ("en" "Cart" "de" "Warenkorb" "fr" "Panier"))
|
|
|
|
("favorites" ("en" "Favorites" "de" "Favoriten" "fr" "Favoris"))
|
|
|
|
("share" ("en" "Share" "de" "Teilen" "fr" "Partager"))
|
|
|
|
("download" ("en" "Download" "de" "Herunterladen" "fr" "Télécharger"))
|
|
|
|
("print" ("en" "Print" "de" "Drucken" "fr" "Imprimer"))
|
|
|
|
("back" ("en" "Back" "de" "Zurück" "fr" "Retour"))
|
|
|
|
("create-account" ("en" "Create Account" "de" "Konto erstellen" "fr" "Créer un compte"))
|
|
|
|
("learn-more" ("en" "Learn More" "de" "Mehr erfahren" "fr" "En savoir plus")))
|
2023-09-13 21:23:19 +02:00
|
|
|
"Localization (l10n) settings for multi-language support.")
|
|
|
|
|
2023-09-22 15:20:19 +02:00
|
|
|
(declaim (ftype (function (string string dict) string) find-l10n))
|
2023-09-22 14:44:25 +02:00
|
|
|
(defun find-l10n (key lang dict)
|
2023-09-13 21:49:19 +02:00
|
|
|
"Finds the localized string for a given key and language."
|
2023-09-22 14:44:25 +02:00
|
|
|
(let ((entry (cadr (assoc key dict :test #'string=))))
|
2023-09-13 21:49:19 +02:00
|
|
|
(if entry
|
|
|
|
(let ((term (cadr (member lang entry :test #'string=))))
|
|
|
|
(or term "Translation not found"))
|
|
|
|
"Key not found")))
|
2023-09-13 21:33:19 +02:00
|
|
|
|
2023-08-25 22:01:55 +02:00
|
|
|
(defmacro with-page ((&key (author "") (description "") (cdn t) (pagetitle "") (theme "dark")) &body body)
|
2023-07-16 13:57:42 +02:00
|
|
|
`(spinneret:with-html
|
|
|
|
(:doctype)
|
2023-08-25 22:01:55 +02:00
|
|
|
(:html :data-bs-theme ,theme
|
2023-07-16 13:57:42 +02:00
|
|
|
(:head
|
2023-07-20 08:32:44 +02:00
|
|
|
(:meta :charset "utf-8")
|
|
|
|
(:meta :name "viewport" :content "width=device-width, initial-scale=1")
|
2023-08-09 14:12:34 +02:00
|
|
|
(:meta :name "author" :content ,author)
|
|
|
|
(:meta :name "description" :content ,description)
|
|
|
|
(:title ,pagetitle)
|
2023-07-20 08:04:24 +02:00
|
|
|
(if ,cdn
|
2023-07-20 08:32:44 +02:00
|
|
|
(:link :type "text/css" :rel "stylesheet" :href ,*cdn-css*)
|
2023-07-20 08:04:24 +02:00
|
|
|
(:link :type "text/css" :rel "stylesheet" :href "5.3.0/bootstrap.min.css")))
|
2023-08-25 22:01:55 +02:00
|
|
|
(:body (:h1 :class "visually-hidden" ,pagetitle)
|
2023-08-25 11:09:35 +02:00
|
|
|
(:main ,@body))
|
2023-07-20 08:04:24 +02:00
|
|
|
(if ,cdn
|
2023-07-20 08:32:44 +02:00
|
|
|
(:script :src *cdn-js*)
|
2023-07-20 08:04:24 +02:00
|
|
|
(:script :src "5.3.0/bootstrap.bundle.min.js")))))
|
2023-07-16 13:57:42 +02:00
|
|
|
|
2023-08-25 15:10:01 +02:00
|
|
|
(defun write-html-to-file (filename string &key (lang "en") (style :tree) (fc 120))
|
|
|
|
(let ((spinneret:*html-lang* lang)
|
|
|
|
(spinneret:*html-style* style)
|
|
|
|
(spinneret:*fill-column* fc))
|
|
|
|
(with-open-file (stream filename :direction :output :if-exists :supersede)
|
|
|
|
(write-string string stream))))
|