Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

147 lines
4.7KB

  1. (in-package #:cl-user)
  2. (defpackage #:cl-deck-builder2.i18n
  3. (:use :cl)
  4. (:import-from :gettext #:*current-locale*)
  5. (:export
  6. #:_
  7. #:n_
  8. #:reload-translations
  9. #:list-loaded-locales
  10. #:*current-locale*
  11. #:set-locale
  12. #:with-locale
  13. #:update-djula.pot
  14. #:generate-templates-list)
  15. (:documentation "Internationalization Utilities"))
  16. (in-package #:cl-deck-builder2.i18n)
  17. (defun setup-gettext ()
  18. (setf djula:*translation-backend* :gettext
  19. (gettext:textdomain) "cl-deck-builder2")
  20. (gettext:setup-gettext #.*package* "cl-deck-builder2"))
  21. (setup-gettext)
  22. (defun reload-translations ()
  23. (format *debug-io* "~%Reading all *.mo files...")
  24. ;; Clear gettext's cache
  25. (clrhash gettext::*catalog-cache*)
  26. (setup-gettext)
  27. (gettext:preload-catalogs
  28. ;; Tell gettext where to find the .mo files
  29. #.(asdf:system-relative-pathname :cl-deck-builder2 "locale/"))
  30. ;; What about this?
  31. (setf djula::*gettext-domain* "cl-deck-builder2"))
  32. ;; Only preload the translations into the image if we're not deployed yet.
  33. (unless (deploy:deployed-p)
  34. (reload-translations))
  35. ;; Run this when developping to reload the translations
  36. #+ (or)
  37. (progn
  38. ;; Clear gettext's cache
  39. (clrhash gettext::*catalog-cache*)
  40. (gettext:preload-catalogs
  41. ;; Tell gettext where to find the .mo files
  42. #.(asdf:system-relative-pathname :cl-deck-builder2 "locale/")))
  43. ;; Run this to see the list of loaded message for a specific locale
  44. #+ (or)
  45. (gettext::catalog-messages
  46. (gethash '("es_ES" :LC_MESSAGES "cl-deck-builder2")
  47. gettext::*catalog-cache*))
  48. ;; Test the translation of a string
  49. #+ (or)
  50. (with-locale ("es_ES")
  51. (_ "Please login to continue"))
  52. #+ (or)
  53. (set-locale "es_ES")
  54. #+ (or)
  55. *current-locale*
  56. (defun list-loaded-locales ()
  57. "Get the list of locales loaded in gettext's cache."
  58. (remove-duplicates
  59. (mapcar #'first
  60. (alexandria:hash-table-keys
  61. gettext::*catalog-cache*))
  62. :test #'string=))
  63. (defun set-locale (locale)
  64. "Setf gettext:*current-locale* and djula:*current-language* if LOCALE seems valid."
  65. ;; It is valid to set the locale to nil.
  66. (when (and locale
  67. (not (member locale (list-loaded-locales)
  68. :test 'string=)))
  69. (error "Locale not valid or not available: ~s" locale))
  70. (setf *current-locale* locale
  71. djula:*current-language* locale))
  72. (defmacro with-locale ((locale) &body body)
  73. "Calls BODY with gettext:*current-locale* and djula:*current-language* set to LOCALE."
  74. `(let (*current-locale* djula:*current-language*)
  75. (set-locale ,locale)
  76. ,@body))
  77. ;; (trace _)
  78. ;; (trace djula:translate gettext:gettext* gettext::lookup)
  79. ;; (_ "hi")
  80. #|
  81. This could technically be just
  82. (mapcan #'djula.locale:file-template-translate-strings
  83. (djula:list-asdf-system-templates :cl-deck-builder2 "templates"))
  84. But I (fstamour) made it just a bit more complex in order to keep track of the source (just the
  85. filename) of each translatable strings. Hence why the hash-table returned is named `locations`.
  86. |#
  87. (defun extract-translate-strings ()
  88. "Extract all {_ ... _} string from the djula templates."
  89. (loop
  90. :with locations = (make-hash-table :test 'equal)
  91. :for path :in (djula:list-asdf-system-templates :cl-deck-builder2 "templates")
  92. :for strings = (djula.locale:file-template-translate-strings path)
  93. :do (loop :for string :in strings
  94. :unless (gethash string locations)
  95. :do (setf (gethash string locations) path))
  96. :finally (return locations)))
  97. (defun update-djula.pot ()
  98. "Update djula.pot from *.html files."
  99. (with-open-file (s (asdf:system-relative-pathname
  100. :cl-deck-builder2 "locale/templates/LC_MESSAGES/djula.pot")
  101. :direction :output
  102. :if-exists :supersede
  103. :if-does-not-exist :create)
  104. (let* ((locations (extract-translate-strings))
  105. (strings (alexandria:hash-table-keys locations)))
  106. (loop
  107. :for string :in strings
  108. :for location = (gethash string locations)
  109. :do
  110. (format s "~%#: ~a~%#, lisp-format~%msgid ~s~%msgstr \"\" ~%"
  111. (enough-namestring
  112. location
  113. (asdf:system-source-directory :cl-deck-builder2))
  114. string)))))
  115. ;; TODO UPDATE-STATIC-TEMPLATE-FILES
  116. (defun generate-templates-list ()
  117. "Generate a list of all files in the \"templates/\" subdirectory, in the form of (:STATIC-FILE \"file...\"). Currently you have to copy/paste this into the ASDF file whenever you add a template."
  118. (let* ((lst '())
  119. (root (asdf:system-relative-pathname :cl-deck-builder2 "templates/")))
  120. (cl-fad:walk-directory
  121. root
  122. (lambda (name)
  123. (push (list :static-file (namestring (uiop:enough-pathname name root))) lst)))
  124. lst))