Line data Source code
1 : package controller
2 :
3 : import (
4 : "net/http"
5 : "os"
6 : "sort"
7 :
8 : "github.com/gin-gonic/gin"
9 : "golang.org/x/text/language"
10 : "golang.org/x/text/language/display"
11 :
12 : "resume/internal/adapter/http/middleware"
13 : repo "resume/internal/domain/repository"
14 : vo "resume/internal/domain/valueobject/i18n"
15 : uci18n "resume/internal/usecase/i18n"
16 : )
17 :
18 : // I18nHandler は i18n 関連エンドポイントのハンドラです。
19 : type I18nHandler struct {
20 : uc uci18n.Usecase
21 : repo repo.DictionaryRepository
22 : }
23 :
24 : // NewI18nHandler は I18nHandler を生成します。
25 : func NewI18nHandler(
26 : uc uci18n.Usecase,
27 : repo repo.DictionaryRepository,
28 0 : ) *I18nHandler {
29 0 : return &I18nHandler{
30 0 : uc: uc,
31 0 : repo: repo,
32 0 : }
33 0 : }
34 :
35 : // GetBundle は prefix で辞書をまとめて返します。
36 : // GET /i18n/bundle?prefix=ui.page.profile.
37 : // lang はミドルウェアで決まったものを使用。
38 : // レスポンスは { "key": "value", ... } のフラットなマップとします。
39 0 : func (h *I18nHandler) GetBundle(c *gin.Context) {
40 0 : loc := vo.NewLocale(middleware.From(c))
41 0 : prefix := c.Query("prefix")
42 0 :
43 0 : out, err := h.uc.ListBundle(c.Request.Context(), uci18n.ListBundleInput{
44 0 : Locale: loc,
45 0 : Prefix: prefix,
46 0 : })
47 0 : if err != nil {
48 0 : c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
49 0 : "code": "i18n_bundle_failed", "message": err.Error(),
50 0 : }})
51 0 : return
52 0 : }
53 : // locale はレスポンスには含めず、辞書だけを返す
54 0 : c.JSON(http.StatusOK, out.Data) // or out.Bundle など、実際のフィールド名に合わせて
55 : }
56 :
57 : // Reload は辞書を再読込し、キャッシュを差し替えます。
58 : // POST /i18n/reload(通常は local か管理者のみ)
59 0 : func (h *I18nHandler) Reload(c *gin.Context) {
60 0 : if os.Getenv("APP_ENV") != "local" {
61 0 : c.Status(http.StatusForbidden)
62 0 : return
63 0 : }
64 0 : if err := h.uc.Reload(c.Request.Context()); err != nil {
65 0 : c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
66 0 : "code": "i18n_reload_failed", "message": err.Error(),
67 0 : }})
68 0 : return
69 0 : }
70 0 : c.Status(http.StatusNoContent)
71 : }
72 :
73 : // ListLocales は利用可能なロケールの一覧を返します。
74 : // GET /i18n/locales
75 0 : func (h *I18nHandler) ListLocales(c *gin.Context) {
76 0 : all, err := h.repo.LoadAll()
77 0 : if err != nil {
78 0 : c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
79 0 : "code": "i18n_list_failed", "message": err.Error(),
80 0 : }})
81 0 : return
82 0 : }
83 0 : type item struct {
84 0 : Code string `json:"code"`
85 0 : Label string `json:"label"`
86 0 : }
87 0 : list := make([]item, 0, len(all))
88 0 : for loc := range all {
89 0 : code := loc.Code()
90 0 : lbl := labelFor(code)
91 0 : list = append(list, item{Code: code, Label: lbl})
92 0 : }
93 0 : sort.Slice(list, func(i, j int) bool { return list[i].Code < list[j].Code })
94 0 : c.JSON(http.StatusOK, gin.H{"locales": list})
95 : }
96 :
97 0 : func labelFor(code string) string {
98 0 : if tag, err := language.Parse(code); err == nil {
99 0 : return display.Self.Name(tag) // 例: ja → 日本語, en → English
100 0 : }
101 0 : return code
102 : }
|