memi-board 0.54.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/dist/{ar-Dm3AFfeA.js → ar-ChyOgoxJ.js} +79 -5
  3. package/dist/ar-ChyOgoxJ.js.map +1 -0
  4. package/dist/{commentImages-BTm9rc5-.js → commentImages-Dm5Jv2vB.js} +303 -203
  5. package/dist/commentImages-Dm5Jv2vB.js.map +1 -0
  6. package/dist/composables/useMemiBoardAppSettings.d.ts +20 -0
  7. package/dist/composables/useMemiBoardPostTranslations.d.ts +17 -0
  8. package/dist/composables/useMemiBoardUserStats.d.ts +11 -0
  9. package/dist/composables/useMemiBoardWritingAssistant.d.ts +6 -0
  10. package/dist/config.d.ts +30 -0
  11. package/dist/{de-B_lTRPBf.js → de-XlC1wNZE.js} +79 -5
  12. package/dist/de-XlC1wNZE.js.map +1 -0
  13. package/dist/{en-CABPmTIs.js → en-XIVIrnU-.js} +79 -5
  14. package/dist/en-XIVIrnU-.js.map +1 -0
  15. package/dist/{es-CpiFVTjt.js → es-DYbCTY9F.js} +79 -5
  16. package/dist/es-DYbCTY9F.js.map +1 -0
  17. package/dist/{fr-NP6NJGIK.js → fr-C0cdV-Mk.js} +79 -5
  18. package/dist/fr-C0cdV-Mk.js.map +1 -0
  19. package/dist/{id-b8OF1gp-.js → id-DfP8Pyvn.js} +79 -5
  20. package/dist/id-DfP8Pyvn.js.map +1 -0
  21. package/dist/index.d.ts +4 -1
  22. package/dist/index.js +1134 -922
  23. package/dist/index.js.map +1 -1
  24. package/dist/{ja-BsqCfwFH.js → ja-DOu3AaTV.js} +79 -5
  25. package/dist/ja-DOu3AaTV.js.map +1 -0
  26. package/dist/module.mjs +23 -1
  27. package/dist/module.mjs.map +1 -1
  28. package/dist/{pt-Co_6IyDe.js → pt-CTeIsQdM.js} +79 -5
  29. package/dist/pt-CTeIsQdM.js.map +1 -0
  30. package/dist/runtime/components/CategorySettings.vue +49 -42
  31. package/dist/runtime/components/CommentAi.vue +2 -2
  32. package/dist/runtime/components/Detail.vue +8 -0
  33. package/dist/runtime/components/Editor.vue +118 -28
  34. package/dist/runtime/components/Footer.vue +63 -20
  35. package/dist/runtime/components/List.vue +2 -0
  36. package/dist/runtime/components/ListCard.vue +91 -0
  37. package/dist/runtime/components/ListViewSwitch.vue +1 -0
  38. package/dist/runtime/components/Profile.vue +34 -1
  39. package/dist/runtime/components/Settings.vue +135 -41
  40. package/dist/storage.js +1 -1
  41. package/dist/types.d.ts +1 -1
  42. package/dist/{zh-DFXcYPOw.js → zh-rDhjLmNl.js} +79 -5
  43. package/dist/zh-rDhjLmNl.js.map +1 -0
  44. package/docs/internationalization.md +6 -4
  45. package/package.json +1 -1
  46. package/dist/ar-Dm3AFfeA.js.map +0 -1
  47. package/dist/commentImages-BTm9rc5-.js.map +0 -1
  48. package/dist/de-B_lTRPBf.js.map +0 -1
  49. package/dist/en-CABPmTIs.js.map +0 -1
  50. package/dist/es-CpiFVTjt.js.map +0 -1
  51. package/dist/fr-NP6NJGIK.js.map +0 -1
  52. package/dist/id-b8OF1gp-.js.map +0 -1
  53. package/dist/ja-BsqCfwFH.js.map +0 -1
  54. package/dist/pt-Co_6IyDe.js.map +0 -1
  55. package/dist/zh-DFXcYPOw.js.map +0 -1
@@ -1,8 +1,80 @@
1
- import { computed as e, getCurrentInstance as t, hasInjectionContext as n, inject as r, onServerPrefetch as i, provide as a, reactive as o, shallowRef as s, toValue as c, watch as l } from "vue";
2
- import { collection as u, doc as d } from "firebase/firestore";
1
+ import { computed as e, getCurrentInstance as t, hasInjectionContext as n, inject as r, onServerPrefetch as i, provide as a, reactive as o, shallowRef as s, toValue as c, watch as ee } from "vue";
2
+ import { collection as l, doc as u } from "firebase/firestore";
3
+ //#region src/i18n/locales.ts
4
+ var d = [
5
+ {
6
+ code: "ko",
7
+ name: "한국어",
8
+ intl: "ko-KR",
9
+ dir: "ltr"
10
+ },
11
+ {
12
+ code: "en",
13
+ name: "English",
14
+ intl: "en-US",
15
+ dir: "ltr"
16
+ },
17
+ {
18
+ code: "ja",
19
+ name: "日本語",
20
+ intl: "ja-JP",
21
+ dir: "ltr"
22
+ },
23
+ {
24
+ code: "de",
25
+ name: "Deutsch",
26
+ intl: "de-DE",
27
+ dir: "ltr"
28
+ },
29
+ {
30
+ code: "fr",
31
+ name: "Français",
32
+ intl: "fr-FR",
33
+ dir: "ltr"
34
+ },
35
+ {
36
+ code: "es",
37
+ name: "Español",
38
+ intl: "es-419",
39
+ dir: "ltr"
40
+ },
41
+ {
42
+ code: "pt",
43
+ name: "Português",
44
+ intl: "pt-BR",
45
+ dir: "ltr"
46
+ },
47
+ {
48
+ code: "zh",
49
+ name: "中文",
50
+ intl: "zh-CN",
51
+ dir: "ltr"
52
+ },
53
+ {
54
+ code: "ar",
55
+ name: "العربية",
56
+ intl: "ar",
57
+ dir: "rtl"
58
+ },
59
+ {
60
+ code: "id",
61
+ name: "Indonesia",
62
+ intl: "id-ID",
63
+ dir: "ltr"
64
+ }
65
+ ], f = "ko", p = "en", m = new Set(d.map((e) => e.code));
66
+ function h(e) {
67
+ if (typeof e != "string") return;
68
+ let t = e.trim().toLowerCase().split(/[-_]/)[0];
69
+ return t && m.has(t) ? t : void 0;
70
+ }
71
+ function g(e) {
72
+ return d.find((t) => t.code === e) ?? d[0];
73
+ }
74
+ //#endregion
3
75
  //#region src/utils/boardPaths.ts
4
- var f = "memiBoardPosts", p = "memiBoardComments", m = "memiBoardLikes", h = "memiBoardReports", g = "memiBoardSettings", _ = "memiBoardUsers";
5
- function v(e) {
76
+ var _ = "memiBoardPosts", v = "memiBoardComments", y = "memiBoardLikes", b = "memiBoardReports", x = "memiBoardSettings", S = "memiBoardUsers";
77
+ function C(e) {
6
78
  return {
7
79
  postsCollection: e.postsCollection?.trim() || "memiBoardPosts",
8
80
  commentsCollection: e.commentsCollection?.trim() || "memiBoardComments",
@@ -12,75 +84,75 @@ function v(e) {
12
84
  usersCollection: e.usersCollection?.trim() || "memiBoardUsers"
13
85
  };
14
86
  }
15
- function ee(e, t) {
16
- return u(e, t.settingsCollection);
87
+ function w(e, t) {
88
+ return l(e, t.settingsCollection);
17
89
  }
18
90
  function te(e, t, n) {
19
- return d(e, t.settingsCollection, n);
91
+ return u(e, t.settingsCollection, n);
20
92
  }
21
93
  function ne(e, t) {
22
- return u(e, t.postsCollection);
94
+ return l(e, t.postsCollection);
23
95
  }
24
- function re(e, t, n) {
25
- return d(e, t.postsCollection, n);
96
+ function T(e, t, n) {
97
+ return u(e, t.postsCollection, n);
26
98
  }
27
- function ie(e, t, n) {
28
- return d(e, t.postsCollection, n, "body", "main");
99
+ function re(e, t, n) {
100
+ return u(e, t.postsCollection, n, "body", "main");
29
101
  }
30
- function ae(e, t) {
31
- return u(e, t.commentsCollection);
102
+ function ie(e, t) {
103
+ return l(e, t.commentsCollection);
32
104
  }
33
- function y(e, t, n) {
34
- return d(e, t.commentsCollection, n);
105
+ function ae(e, t, n) {
106
+ return u(e, t.commentsCollection, n);
35
107
  }
36
- function b(e, t) {
37
- return u(e, t.likesCollection);
108
+ function oe(e, t) {
109
+ return l(e, t.likesCollection);
38
110
  }
39
- function x(e, t) {
111
+ function E(e, t) {
40
112
  return `${e}_${t}`;
41
113
  }
42
- function S(e, t, n, r) {
43
- return d(e, t.likesCollection, x(n, r));
114
+ function se(e, t, n, r) {
115
+ return u(e, t.likesCollection, E(n, r));
44
116
  }
45
- function oe(e, t) {
46
- return u(e, t.reportsCollection);
117
+ function ce(e, t) {
118
+ return l(e, t.reportsCollection);
47
119
  }
48
- function C(e, t) {
120
+ function D(e, t) {
49
121
  return `${e}_${t}`;
50
122
  }
51
- function se(e, t, n, r) {
52
- return d(e, t.reportsCollection, C(n, r));
123
+ function le(e, t, n, r) {
124
+ return u(e, t.reportsCollection, D(n, r));
53
125
  }
54
- function ce(e, t) {
55
- return u(e, t.usersCollection);
126
+ function O(e, t) {
127
+ return l(e, t.usersCollection);
56
128
  }
57
- function le(e, t, n) {
58
- return d(e, t.usersCollection, n);
129
+ function k(e, t, n) {
130
+ return u(e, t.usersCollection, n);
59
131
  }
60
- function ue(e, t, n) {
61
- return d(e, t.usersCollection, n, "private", "info");
132
+ function A(e, t, n) {
133
+ return u(e, t.usersCollection, n, "private", "info");
62
134
  }
63
- function de(e, t, n, r) {
64
- return d(e, t.usersCollection, n, "memos", r);
135
+ function ue(e, t, n, r) {
136
+ return u(e, t.usersCollection, n, "memos", r);
65
137
  }
66
- function fe(e, t) {
138
+ function de(e, t) {
67
139
  return `${e.postsCollection}/${t}`;
68
140
  }
69
- function pe(e, t) {
141
+ function fe(e, t) {
70
142
  return `${e.postsCollection}/${e.usersCollection}/${t}`;
71
143
  }
72
144
  //#endregion
73
145
  //#region src/config.ts
74
- var w = "__MEMI_BOARD_CONFIG__";
75
- function T() {
146
+ var j = "__MEMI_BOARD_CONFIG__";
147
+ function M() {
76
148
  let e = globalThis;
77
- return e[w] || (e[w] = o({
78
- postsCollection: f,
79
- commentsCollection: p,
80
- likesCollection: m,
81
- reportsCollection: h,
82
- settingsCollection: g,
83
- usersCollection: _,
149
+ return e[j] || (e[j] = o({
150
+ postsCollection: _,
151
+ commentsCollection: v,
152
+ likesCollection: y,
153
+ reportsCollection: b,
154
+ settingsCollection: x,
155
+ usersCollection: S,
84
156
  auth: { providers: ["google", "apple"] },
85
157
  moderation: {
86
158
  enabled: !0,
@@ -98,10 +170,10 @@ function T() {
98
170
  defaultOgImage: "",
99
171
  basePath: "/board"
100
172
  }
101
- })), e[w];
173
+ })), e[j];
102
174
  }
103
- function me(e) {
104
- let t = T();
175
+ function pe(e) {
176
+ let t = M();
105
177
  e.postsCollection != null && e.postsCollection !== "" && (t.postsCollection = e.postsCollection.trim()), e.commentsCollection != null && e.commentsCollection !== "" && (t.commentsCollection = e.commentsCollection.trim()), e.likesCollection != null && e.likesCollection !== "" && (t.likesCollection = e.likesCollection.trim()), e.reportsCollection != null && e.reportsCollection !== "" && (t.reportsCollection = e.reportsCollection.trim()), e.settingsCollection != null && e.settingsCollection !== "" && (t.settingsCollection = e.settingsCollection.trim()), e.usersCollection != null && e.usersCollection !== "" && (t.usersCollection = e.usersCollection.trim()), e.auth && Object.assign(t.auth, e.auth), e.moderation && Object.assign(t.moderation, e.moderation), e.seo && Object.assign(t.seo, e.seo), e.i18n && (t.i18n = {
106
178
  ...t.i18n,
107
179
  ...e.i18n
@@ -110,96 +182,49 @@ function me(e) {
110
182
  ...e.translation
111
183
  }), e.roleLabels && (t.roleLabels = { ...e.roleLabels }), e.locale && (t.locale = e.locale);
112
184
  }
113
- function E() {
114
- return T();
185
+ function N() {
186
+ return M();
115
187
  }
116
- function D() {
117
- return E().i18n?.enabled !== !1;
188
+ function P() {
189
+ return N().site?.disableAi !== !0;
118
190
  }
119
- function O() {
120
- return D() && E().translation?.enabled !== !1;
191
+ function me() {
192
+ if (!P()) throw Object.assign(/* @__PURE__ */ Error("AI features are disabled for this board service."), { code: "memi-board/ai-disabled" });
121
193
  }
122
- function k() {
123
- if (!O()) throw Object.assign(/* @__PURE__ */ Error("Content translation is disabled for this board service."), { code: "memi-board/translation-disabled" });
194
+ function F() {
195
+ let e = N();
196
+ return e.site?.disableI18n !== !0 && e.i18n?.enabled !== !1;
124
197
  }
125
- function A() {
126
- return v(E());
198
+ function I() {
199
+ return P() && F() && N().translation?.enabled !== !1;
127
200
  }
128
- //#endregion
129
- //#region src/i18n/locales.ts
130
- var j = [
131
- {
132
- code: "ko",
133
- name: "한국어",
134
- intl: "ko-KR",
135
- dir: "ltr"
136
- },
137
- {
138
- code: "en",
139
- name: "English",
140
- intl: "en-US",
141
- dir: "ltr"
142
- },
143
- {
144
- code: "ja",
145
- name: "日本語",
146
- intl: "ja-JP",
147
- dir: "ltr"
148
- },
149
- {
150
- code: "de",
151
- name: "Deutsch",
152
- intl: "de-DE",
153
- dir: "ltr"
154
- },
155
- {
156
- code: "fr",
157
- name: "Français",
158
- intl: "fr-FR",
159
- dir: "ltr"
160
- },
161
- {
162
- code: "es",
163
- name: "Español",
164
- intl: "es-419",
165
- dir: "ltr"
166
- },
167
- {
168
- code: "pt",
169
- name: "Português",
170
- intl: "pt-BR",
171
- dir: "ltr"
172
- },
173
- {
174
- code: "zh",
175
- name: "中文",
176
- intl: "zh-CN",
177
- dir: "ltr"
178
- },
179
- {
180
- code: "ar",
181
- name: "العربية",
182
- intl: "ar",
183
- dir: "rtl"
184
- },
185
- {
186
- code: "id",
187
- name: "Indonesia",
188
- intl: "id-ID",
189
- dir: "ltr"
190
- }
191
- ], M = "ko", N = "en", P = new Set(j.map((e) => e.code));
192
- function F(e) {
193
- if (typeof e != "string") return;
194
- let t = e.trim().toLowerCase().split(/[-_]/)[0];
195
- return t && P.has(t) ? t : void 0;
201
+ function L() {
202
+ let e = d.map((e) => e.code), t = N().site?.locales;
203
+ if (!F() || !t?.length) return e;
204
+ let n = new Set(t.map((e) => h(e)).filter(Boolean)), r = e.filter((e) => n.has(e));
205
+ return r.length ? r : e;
196
206
  }
197
- function I(e) {
198
- return j.find((t) => t.code === e) ?? j[0];
207
+ function R() {
208
+ let e = N(), t = e.site?.locale || e.locale, n = L(), r = h(t);
209
+ return n.length === d.length || r && n.includes(r) ? t : n[0];
210
+ }
211
+ function z(e) {
212
+ N().site = {
213
+ disableAi: e?.disableAi === !0,
214
+ disableI18n: e?.disableI18n === !0,
215
+ locale: typeof e?.locale == "string" && e.locale ? e.locale : void 0,
216
+ locales: Array.isArray(e?.locales) ? e.locales.filter((e) => typeof e == "string") : void 0
217
+ };
218
+ }
219
+ function he() {
220
+ if (!I()) throw Object.assign(/* @__PURE__ */ Error("Content translation is disabled for this board service."), { code: "memi-board/translation-disabled" });
221
+ }
222
+ function ge() {
223
+ return C(N());
199
224
  }
200
225
  //#endregion
201
226
  //#region src/i18n/messages/ko/index.ts
202
- var L = {
227
+ var _e = {
203
228
  attachments: {
204
229
  attach: "파일 첨부 ({count} / {max})",
205
230
  dropOrClick: "파일을 놓거나 클릭해서 추가",
@@ -414,6 +439,10 @@ var L = {
414
439
  help: "예: 경상도 사투리 스타일로 / 정중한 문어체로 / 간결한 개조식으로",
415
440
  placeholder: "경상도 사투리 스타일로"
416
441
  },
442
+ translateView: {
443
+ title: "{language} 번역 미리 보기",
444
+ notSaved: "저장되지 않았습니다. 미리 보기일 뿐입니다."
445
+ },
417
446
  aiPreview: {
418
447
  title: "AI 결과 확인",
419
448
  currentTitle: "현재 제목",
@@ -474,9 +503,8 @@ var L = {
474
503
  summarize: "짧게 요약하기",
475
504
  translateEn: "영어로 번역",
476
505
  translateKo: "한국어로 번역",
506
+ translateView: "번역해서 보기",
477
507
  translateAll: "{count}개 언어로 번역해 저장",
478
- translateSaved: "{count}개 언어로 저장했습니다. 다른 언어로 보면 번역이 보입니다.",
479
- translatePending: "{count}개 언어 번역을 준비했습니다. 글을 저장하면 함께 기록됩니다.",
480
508
  translateEmpty: "번역할 제목이나 본문을 먼저 입력해 주세요.",
481
509
  title: "본문에서 제목 만들기",
482
510
  custom: "커스텀 스타일로 바꾸기…",
@@ -510,7 +538,10 @@ var L = {
510
538
  writeRestricted: "콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요.",
511
539
  moderationBlocked: "게시할 수 없는 내용이 포함되어 있습니다."
512
540
  },
541
+ titleOptionalPlaceholder: "제목 (비워 두면 AI가 만들어 드려요)",
542
+ titleAiNotice: "AI가 글 내용으로 제목을 만들었어요. 마음에 들면 저장하고, 아니면 고쳐 주세요.",
513
543
  hint: {
544
+ titling: "제목을 만드는 중…",
514
545
  translating: "글을 {count}개 언어로 번역하는 중…",
515
546
  reviewing: "내용을 검토하는 중…",
516
547
  saving: "저장하는 중…"
@@ -520,9 +551,13 @@ var L = {
520
551
  introduction: "memi-board는 Nuxt 4와 Vue 3, TypeScript를 바탕으로 만들었습니다. Nuxt UI와 Firebase로 글과 댓글, 사람과 이야기를 자연스럽게 이어갑니다.",
521
552
  eyebrow: "함께 만드는 이야기",
522
553
  tagline: "글과 댓글로 이어지는 작은 커뮤니티.",
554
+ detail: "자세한 소개",
523
555
  links: "링크 & 연락처",
524
556
  email: "이메일",
525
557
  credit: "만든 이: memi",
558
+ feedback: "피드백 보내기",
559
+ feedbackSubject: "memi-board 피드백",
560
+ feedbackBody: "의견:\n",
526
561
  siteEyebrow: "memi-board와 함께",
527
562
  changelog: "변경 기록",
528
563
  info: {
@@ -537,7 +572,13 @@ var L = {
537
572
  waiting: "QR로 간편하게 시작하는 대기 서비스",
538
573
  track: "이동 경로를 기록하는 GPS 트래킹",
539
574
  loop: "차량 운행 기록과 센서 솔루션",
540
- safe: "페달 오조작 방지 장치 연결 및 설정"
575
+ safe: "페달 오조작 방지 장치 연결 및 설정",
576
+ inviteTitle: "당신도 쓰고 계신가요?",
577
+ invite: "사이트 주소를 알려주세요~",
578
+ inviteSite: "memi.dev에 남기기",
579
+ inviteOr: "또는",
580
+ inviteSubject: "memi-board를 쓰고 있어요",
581
+ inviteBody: "사이트 주소:\n"
541
582
  },
542
583
  stack: {
543
584
  ui: "화면과 경험",
@@ -555,6 +596,7 @@ var L = {
555
596
  loadFailed: "목록을 불러오지 못했습니다: {error}",
556
597
  empty: "아직 게시글이 없습니다.",
557
598
  notice: "공지",
599
+ commentCount: "댓글 {count}개",
558
600
  photo: "사진",
559
601
  likeCount: "추천 {count}",
560
602
  info: {
@@ -583,6 +625,7 @@ var L = {
583
625
  },
584
626
  listViewSwitch: {
585
627
  default: "일반",
628
+ card: "카드",
586
629
  dense: "조밀",
587
630
  video: "영상"
588
631
  },
@@ -632,7 +675,14 @@ var L = {
632
675
  notFound: "사용자를 찾을 수 없습니다.",
633
676
  noName: "이름 없음",
634
677
  joined: "가입 {time}",
635
- lastVisit: "최근 방문 {time}"
678
+ lastVisit: "최근 방문 {time}",
679
+ stats: {
680
+ posts: "글 {count}",
681
+ comments: "댓글 {count}",
682
+ aiUsage: "AI 사용 {used} / {limit}",
683
+ aiUsageResets: "AI 사용 {used} / {limit} · {hours}시간 후 초기화",
684
+ warnings: "경고 {count}회"
685
+ }
636
686
  },
637
687
  profileEditModal: {
638
688
  imageOnly: "이미지 파일만 선택할 수 있습니다.",
@@ -787,6 +837,7 @@ var L = {
787
837
  label: "리스트뷰",
788
838
  help: "게시글 목록 표시 방식",
789
839
  options: {
840
+ card: "카드",
790
841
  dense: "조밀",
791
842
  video: "영상"
792
843
  }
@@ -868,6 +919,54 @@ var L = {
868
919
  saveFailed: "게시판 설정을 저장하지 못했습니다.",
869
920
  deleteAllFailed: "게시판 데이터를 삭제하지 못했습니다."
870
921
  },
922
+ aiTitle: {
923
+ label: "AI 제목 자동 만들기",
924
+ help: "새 글의 제목을 비우면 AI가 글(글이 없으면 첫 사진)로 제목을 만들어 줍니다. 사진은 제목을 만들 때만 Google AI로 전송됩니다. 끄면 제목을 직접 써야 하고 사진도 전송되지 않습니다."
925
+ },
926
+ site: {
927
+ apply: "적용",
928
+ applied: "적용했습니다.",
929
+ title: "전체 설정",
930
+ help: "사이트 전체에 적용됩니다. 호스트 앱의 설정 위에 덧씌워지고, 바꾸면 바로 반영됩니다.",
931
+ ai: {
932
+ label: "AI 기능 사용",
933
+ help: "비용 등으로 AI를 쓰지 않을 때 끄세요. 끄면 번역, 글쓰기 도우미, AI 제목, AI 검열이 모두 꺼지고 사진도 AI로 전송되지 않습니다. 금칙어 검사는 그대로 동작합니다."
934
+ },
935
+ i18n: {
936
+ label: "국제화 사용",
937
+ help: "끄면 모든 화면이 아래에서 고른 한 언어로 고정되고 번역 기능도 꺼집니다. 이미 저장된 번역은 지워지지 않습니다."
938
+ },
939
+ locales: {
940
+ label: "사용할 언어",
941
+ help: "선택한 {count}개 언어만 번역하고 언어 메뉴에 보여 줍니다. 고르지 않은 언어는 새로 번역하지 않으며, 이미 저장된 번역은 지워지지 않습니다."
942
+ },
943
+ locale: {
944
+ label: "기본 언어",
945
+ help: "호스트 앱이 언어를 정하지 않을 때 쓰는 언어입니다.",
946
+ fixedLabel: "고정 언어",
947
+ fixedHelp: "모든 화면이 이 언어로 표시됩니다."
948
+ }
949
+ },
950
+ i18nBoard: {
951
+ label: "국제화",
952
+ help: "켜면 게시판 이름·설명과 새 글·댓글을 {count}개 언어로 자동 번역해 함께 저장합니다. 끄면 원문만 보입니다.",
953
+ existing: "기존 글도 번역하기"
954
+ },
955
+ about: {
956
+ label: "About memi-board 표시",
957
+ help: "화면 아래의 About memi-board 링크와 소개 팝업을 모든 게시판 화면에 보여 줍니다. 끄면 사이트 전체에서 숨겨집니다."
958
+ },
959
+ postTranslations: {
960
+ label: "기존 글 번역",
961
+ help: "번역이 없거나 원문과 달라진 글을 찾아 한꺼번에 번역하고 저장합니다. 자동 번역을 끈 게시판이나, 번역이 빠진 글을 채울 때 쓰세요. AI 사용 횟수는 차감하지 않습니다.",
962
+ scan: "번역할 글 찾기",
963
+ run: "{count}개 글 번역하기",
964
+ confirm: "{count}개 글을 번역해 저장합니다. 글 수만큼 AI 호출이 일어납니다. 계속할까요?",
965
+ progress: "번역 중… 완료 {done}, 실패 {failed}",
966
+ allDone: "게시된 글 {total}개 모두 번역이 최신입니다.",
967
+ found: "게시된 글 {total}개 중 {count}개를 번역해야 합니다.",
968
+ failed: "{count}개 글은 번역하지 못했습니다. 다시 찾아서 시도해 주세요."
969
+ },
871
970
  cleanup: {
872
971
  contextChanged: "조회 이후 계정이나 게시판 설정이 변경되었습니다. 다시 조회해 주세요.",
873
972
  title: "고아 파일·문서 정리",
@@ -964,40 +1063,40 @@ var L = {
964
1063
  custom: "커스텀 스타일로 바꾸기…"
965
1064
  }
966
1065
  }
967
- }, R = {
968
- en: () => import("./en-CABPmTIs.js"),
969
- ja: () => import("./ja-BsqCfwFH.js"),
970
- de: () => import("./de-B_lTRPBf.js"),
971
- fr: () => import("./fr-NP6NJGIK.js"),
972
- es: () => import("./es-CpiFVTjt.js"),
973
- pt: () => import("./pt-Co_6IyDe.js"),
974
- zh: () => import("./zh-DFXcYPOw.js"),
975
- ar: () => import("./ar-Dm3AFfeA.js"),
976
- id: () => import("./id-b8OF1gp-.js")
977
- }, z = "__MEMI_BOARD_I18N__";
978
- function B() {
1066
+ }, ve = {
1067
+ en: () => import("./en-XIVIrnU-.js"),
1068
+ ja: () => import("./ja-DOu3AaTV.js"),
1069
+ de: () => import("./de-XlC1wNZE.js"),
1070
+ fr: () => import("./fr-C0cdV-Mk.js"),
1071
+ es: () => import("./es-DYbCTY9F.js"),
1072
+ pt: () => import("./pt-CTeIsQdM.js"),
1073
+ zh: () => import("./zh-rDhjLmNl.js"),
1074
+ ar: () => import("./ar-ChyOgoxJ.js"),
1075
+ id: () => import("./id-DfP8Pyvn.js")
1076
+ }, B = "__MEMI_BOARD_I18N__";
1077
+ function V() {
979
1078
  let e = globalThis;
980
- return e[z] ??= {
981
- messages: { ko: L },
1079
+ return e[B] ??= {
1080
+ messages: { ko: _e },
982
1081
  pending: {},
983
1082
  version: s(0)
984
- }, e[z];
1083
+ }, e[B];
985
1084
  }
986
- function V(e) {
987
- let t = B();
988
- return t.messages[e] || e === "ko" ? Promise.resolve() : (t.pending[e] ??= R[e]().then((n) => {
1085
+ function H(e) {
1086
+ let t = V();
1087
+ return t.messages[e] || e === "ko" ? Promise.resolve() : (t.pending[e] ??= ve[e]().then((n) => {
989
1088
  t.messages[e] = n.default, t.version.value++;
990
1089
  }).catch((n) => {
991
1090
  delete t.pending[e], console.warn(`[memi-board] failed to load locale "${e}"`, n);
992
1091
  }), t.pending[e]);
993
1092
  }
994
- function H(e) {
995
- return e === "ko" ? Promise.resolve() : Promise.all([V(e), V("en")]).then(() => void 0);
1093
+ function U(e) {
1094
+ return e === "ko" ? Promise.resolve() : Promise.all([H(e), H("en")]).then(() => void 0);
996
1095
  }
997
- function he(e) {
998
- return !!B().messages[e];
1096
+ function ye(e) {
1097
+ return !!V().messages[e];
999
1098
  }
1000
- function U(e, t) {
1099
+ function W(e, t) {
1001
1100
  let n = e;
1002
1101
  for (let e of t.split(".")) {
1003
1102
  if (!n || typeof n == "string") return;
@@ -1005,36 +1104,36 @@ function U(e, t) {
1005
1104
  }
1006
1105
  return typeof n == "string" ? n : void 0;
1007
1106
  }
1008
- function ge(e, t) {
1107
+ function be(e, t) {
1009
1108
  return t ? e.replace(/\{(\w+)\}/g, (e, n) => {
1010
1109
  let r = t[n];
1011
1110
  return r == null ? e : String(r);
1012
1111
  }) : e;
1013
1112
  }
1014
- function W(e, t, n) {
1015
- let r = B();
1113
+ function G(e, t, n) {
1114
+ let r = V();
1016
1115
  r.version.value;
1017
- let i = e ?? "ko", a = U(r.messages[i], t) ?? (i === "ko" ? void 0 : U(r.messages.en, t)) ?? U(r.messages.ko, t);
1018
- return a == null ? t : ge(a, n);
1116
+ let i = e ?? "ko", a = W(r.messages[i], t) ?? (i === "ko" ? void 0 : W(r.messages.en, t)) ?? W(r.messages.ko, t);
1117
+ return a == null ? t : be(a, n);
1019
1118
  }
1020
1119
  //#endregion
1021
1120
  //#region src/utils/formatDate.ts
1022
- function G(e) {
1023
- return I(e ?? "ko").intl;
1121
+ function K(e) {
1122
+ return g(e ?? "ko").intl;
1024
1123
  }
1025
- function K(e, t) {
1026
- return e?.toDate ? e.toDate().toLocaleDateString(G(t)) : "";
1124
+ function q(e, t) {
1125
+ return e?.toDate ? e.toDate().toLocaleDateString(K(t)) : "";
1027
1126
  }
1028
- var _e = 60, q = 3600, ve = 24 * q;
1029
- function J(e, t = Date.now(), n) {
1030
- if (!e?.toDate) return W(n, "common.date.justNow");
1127
+ var xe = 60, J = 3600, Se = 24 * J;
1128
+ function Y(e, t = Date.now(), n) {
1129
+ if (!e?.toDate) return G(n, "common.date.justNow");
1031
1130
  let r = Math.max(0, Math.round((t - e.toDate().getTime()) / 1e3));
1032
- if (r < 45) return W(n, "common.date.fewSecondsAgo");
1033
- let i = new Intl.RelativeTimeFormat(G(n), { numeric: "always" }), a = r / ve;
1034
- return r < 90 ? i.format(-1, "minute") : r < 2700 ? i.format(-Math.round(r / _e), "minute") : r < 5400 ? i.format(-1, "hour") : r < 22 * q ? i.format(-Math.round(r / q), "hour") : r < 36 * q ? i.format(-1, "day") : a < 26 ? i.format(-Math.round(a), "day") : a < 46 ? i.format(-1, "month") : a < 320 ? i.format(-Math.round(a / 30.4), "month") : a < 548 ? i.format(-1, "year") : i.format(-Math.round(a / 365), "year");
1131
+ if (r < 45) return G(n, "common.date.fewSecondsAgo");
1132
+ let i = new Intl.RelativeTimeFormat(K(n), { numeric: "always" }), a = r / Se;
1133
+ return r < 90 ? i.format(-1, "minute") : r < 2700 ? i.format(-Math.round(r / xe), "minute") : r < 5400 ? i.format(-1, "hour") : r < 22 * J ? i.format(-Math.round(r / J), "hour") : r < 36 * J ? i.format(-1, "day") : a < 26 ? i.format(-Math.round(a), "day") : a < 46 ? i.format(-1, "month") : a < 320 ? i.format(-Math.round(a / 30.4), "month") : a < 548 ? i.format(-1, "year") : i.format(-Math.round(a / 365), "year");
1035
1134
  }
1036
- function Y(e, t) {
1037
- return e?.toDate ? new Intl.DateTimeFormat(G(t), {
1135
+ function X(e, t) {
1136
+ return e?.toDate ? new Intl.DateTimeFormat(K(t), {
1038
1137
  year: "numeric",
1039
1138
  month: "long",
1040
1139
  day: "numeric",
@@ -1042,56 +1141,57 @@ function Y(e, t) {
1042
1141
  minute: "2-digit",
1043
1142
  second: "2-digit",
1044
1143
  hourCycle: "h23"
1045
- }).format(e.toDate()) : W(t, "common.date.checking");
1144
+ }).format(e.toDate()) : G(t, "common.date.checking");
1046
1145
  }
1047
- function X(e, t, n) {
1048
- let r = Y(e, n), i = e?.toMillis?.(), a = t?.toMillis?.();
1049
- return a == null || i == null || i === a ? [r] : [W(n, "common.date.createdAt", { time: r }), W(n, "common.date.updatedAt", { time: Y(t, n) })];
1146
+ function Z(e, t, n) {
1147
+ let r = X(e, n), i = e?.toMillis?.(), a = t?.toMillis?.();
1148
+ return a == null || i == null || i === a ? [r] : [G(n, "common.date.createdAt", { time: r }), G(n, "common.date.updatedAt", { time: X(t, n) })];
1050
1149
  }
1051
1150
  //#endregion
1052
1151
  //#region src/i18n/useMemiBoardI18n.ts
1053
- var Z = "memiBoard:hostLocale", Q = Symbol("memiBoard:boardLocale");
1054
- function ye(o = {}) {
1055
- let s = n(), u = s ? r(Q, null) : null, d = s ? r(Z, null) : null, f = E(), p = e(D), m = e(O), h = e(() => p.value ? F(c(o.boardLocale)) ?? u?.value ?? F(c(d)) ?? F(f.locale) ?? "ko" : F(f.locale) ?? "ko");
1056
- o.boardLocale !== void 0 && s && a(Q, h), l(h, (e) => void H(e), { immediate: !0 }), t() && i(() => H(h.value));
1057
- let g = e(() => I(h.value));
1152
+ var Q = "memiBoard:hostLocale", $ = Symbol("memiBoard:boardLocale");
1153
+ function Ce(o = {}) {
1154
+ let s = n(), l = s ? r($, null) : null, u = s ? r(Q, null) : null, d = N(), f = e(F), p = e(I), m = e(P), _ = e(() => f.value ? h(c(o.boardLocale)) ?? l?.value ?? h(c(u)) ?? h(R()) ?? "ko" : h(R()) ?? "ko");
1155
+ o.boardLocale !== void 0 && s && a($, _), ee(_, (e) => void U(e), { immediate: !0 }), t() && i(() => U(_.value));
1156
+ let v = e(() => g(_.value));
1058
1157
  return {
1059
- locale: h,
1060
- i18nEnabled: p,
1061
- translationEnabled: m,
1062
- dir: e(() => g.value.dir),
1063
- intlLocale: e(() => g.value.intl),
1158
+ locale: _,
1159
+ i18nEnabled: f,
1160
+ translationEnabled: p,
1161
+ aiEnabled: m,
1162
+ dir: e(() => v.value.dir),
1163
+ intlLocale: e(() => v.value.intl),
1064
1164
  t: (e, t) => {
1065
1165
  let n = e.startsWith("common.role.") ? e.slice(12) : "";
1066
- return f.roleLabels?.[n] ?? W(h.value, e, t);
1166
+ return d.roleLabels?.[n] ?? G(_.value, e, t);
1067
1167
  },
1068
- formatDate: (e) => K(e, h.value),
1069
- formatFullDate: (e) => Y(e, h.value),
1070
- formatRelativeDate: (e, t) => J(e, t, h.value),
1071
- formatTimestampDetails: (e, t) => X(e, t, h.value),
1072
- formatNumber: (e) => e.toLocaleString(g.value.intl)
1168
+ formatDate: (e) => q(e, _.value),
1169
+ formatFullDate: (e) => X(e, _.value),
1170
+ formatRelativeDate: (e, t) => Y(e, t, _.value),
1171
+ formatTimestampDetails: (e, t) => Z(e, t, _.value),
1172
+ formatNumber: (e) => e.toLocaleString(v.value.intl)
1073
1173
  };
1074
1174
  }
1075
1175
  //#endregion
1076
1176
  //#region src/utils/slugify.ts
1077
- var be = 40;
1078
- function xe(e) {
1177
+ var we = 40;
1178
+ function Te(e) {
1079
1179
  return e.trim().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎㅏ-ㅣ-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
1080
1180
  }
1081
- function Se(e, t = 60) {
1181
+ function Ee(e, t = 60) {
1082
1182
  return e.replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎ._-]/g, "-").slice(0, t);
1083
1183
  }
1084
1184
  //#endregion
1085
1185
  //#region src/utils/commentImages.ts
1086
- var Ce = /* @__PURE__ */ new Set([
1186
+ var De = /* @__PURE__ */ new Set([
1087
1187
  "image/jpeg",
1088
1188
  "image/png",
1089
1189
  "image/gif",
1090
1190
  "image/webp"
1091
1191
  ]);
1092
- function $(e) {
1192
+ function Oe(e) {
1093
1193
  let t = (e.type || "").toLowerCase();
1094
- if (Ce.has(t)) return !0;
1194
+ if (De.has(t)) return !0;
1095
1195
  let n = e.name.toLowerCase();
1096
1196
  return [
1097
1197
  ".jpg",
@@ -1101,7 +1201,7 @@ function $(e) {
1101
1201
  ".webp"
1102
1202
  ].some((e) => n.endsWith(e));
1103
1203
  }
1104
- function we(e) {
1204
+ function ke(e) {
1105
1205
  if (!e?.length) return [];
1106
1206
  for (let t of e) if (t?.name && t.url?.startsWith("https://") && t.path && !t.path.includes("..") && t.type?.startsWith("image/") && !(!Number.isFinite(t.size) || t.size <= 0)) return [{
1107
1207
  name: t.name.slice(0, 200),
@@ -1112,7 +1212,7 @@ function we(e) {
1112
1212
  }];
1113
1213
  return [];
1114
1214
  }
1115
- function Te(e, t) {
1215
+ function Ae(e, t) {
1116
1216
  let n = t.endsWith("/") ? t : `${t}/`;
1117
1217
  if (!Array.isArray(e)) return [];
1118
1218
  let r = /* @__PURE__ */ new Set();
@@ -1124,6 +1224,6 @@ function Te(e, t) {
1124
1224
  return [...r];
1125
1225
  }
1126
1226
  //#endregion
1127
- export { h as A, S as B, D as C, p as D, E, le as F, fe as G, b as H, ue as I, C as J, ne as K, ce as L, _ as M, de as N, m as O, pe as P, te as Q, y as R, me as S, A as T, ie as U, x as V, re as W, v as X, oe as Y, ee as Z, N as _, Se as a, F as b, ye as c, J as d, X as f, M as g, W as h, be as i, g as j, f as k, K as l, H as m, $ as n, xe as o, he as p, se as q, we as r, Z as s, Te as t, Y as u, j as v, O as w, k as x, I as y, ae as z };
1227
+ export { f as $, b as A, se as B, R as C, v as D, N as E, k as F, de as G, oe as H, A as I, D as J, ne as K, O as L, S as M, ue as N, y as O, fe as P, te as Q, ae as R, I as S, ge as T, re as U, E as V, T as W, C as X, ce as Y, w as Z, me as _, Ee as a, P as b, Ce as c, Y as d, p as et, Z as f, z as g, G as h, we as i, x as j, _ as k, q as l, U as m, Oe as n, g as nt, Te as o, ye as p, le as q, ke as r, h as rt, Q as s, Ae as t, d as tt, X as u, he as v, L as w, F as x, pe as y, ie as z };
1128
1228
 
1129
- //# sourceMappingURL=commentImages-BTm9rc5-.js.map
1229
+ //# sourceMappingURL=commentImages-Dm5Jv2vB.js.map