Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

147 行
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))