memi-board 0.45.1 → 0.46.1

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 (63) hide show
  1. package/README.md +21 -6
  2. package/dist/{ar-BCQdRw_S.js → ar-2vagHrlh.js} +17 -1
  3. package/dist/ar-2vagHrlh.js.map +1 -0
  4. package/dist/composables/boardLookup.d.ts +2 -0
  5. package/dist/composables/fetchPublicSeo.d.ts +1 -1
  6. package/dist/composables/useMemiBoardAuth.d.ts +5 -1
  7. package/dist/composables/useMemiBoardAutoTranslate.d.ts +6 -6
  8. package/dist/composables/useMemiBoardPosts.d.ts +6 -1
  9. package/dist/composables/useMemiBoardSettings.d.ts +5 -4
  10. package/dist/composables/useMemiBoardWritingAssistant.d.ts +8 -0
  11. package/dist/config.d.ts +2 -0
  12. package/dist/{de-ddWQNaqs.js → de-B6gqgrqZ.js} +17 -1
  13. package/dist/de-B6gqgrqZ.js.map +1 -0
  14. package/dist/{en-DxkqyZq2.js → en-BaP_ikG-.js} +20 -2
  15. package/dist/en-BaP_ikG-.js.map +1 -0
  16. package/dist/{es-7JHjGVlM.js → es-CGxYBYNH.js} +17 -1
  17. package/dist/es-CGxYBYNH.js.map +1 -0
  18. package/dist/{fr-C3NCMNoy.js → fr-DeP9Swrv.js} +17 -1
  19. package/dist/fr-DeP9Swrv.js.map +1 -0
  20. package/dist/{id-C8hCAMPJ.js → id-BIUlVuq8.js} +17 -1
  21. package/dist/id-BIUlVuq8.js.map +1 -0
  22. package/dist/index.d.ts +3 -2
  23. package/dist/index.js +1123 -757
  24. package/dist/index.js.map +1 -1
  25. package/dist/{ja-oTWri4hY.js → ja-0FOftjGv.js} +17 -1
  26. package/dist/{ja-oTWri4hY.js.map → ja-0FOftjGv.js.map} +1 -1
  27. package/dist/{pt-B9EH4scA.js → pt-D8IArHb-.js} +17 -1
  28. package/dist/pt-D8IArHb-.js.map +1 -0
  29. package/dist/runtime/components/CategorySettings.vue +39 -15
  30. package/dist/runtime/components/Detail.vue +9 -7
  31. package/dist/runtime/components/Editor.vue +79 -8
  32. package/dist/runtime/components/List.vue +2 -2
  33. package/dist/runtime/components/ListDefault.vue +16 -6
  34. package/dist/runtime/components/ListDense.vue +8 -4
  35. package/dist/runtime/components/ListImage.vue +13 -6
  36. package/dist/runtime/components/ListVideo.vue +14 -5
  37. package/dist/runtime/components/Section.vue +10 -3
  38. package/dist/runtime/components/Settings.vue +35 -11
  39. package/dist/runtime/components/Users.vue +4 -3
  40. package/dist/runtime/composables/useMemiBoardSeo.ts +3 -3
  41. package/dist/{slugify-BuOMFLxN.js → slugify-BR-FLuDb.js} +34 -13
  42. package/dist/slugify-BR-FLuDb.js.map +1 -0
  43. package/dist/storage.js +1 -1
  44. package/dist/types.d.ts +46 -1
  45. package/dist/utils/boardAccess.d.ts +3 -0
  46. package/dist/utils/localizedFields.d.ts +33 -0
  47. package/dist/utils/section.d.ts +1 -1
  48. package/dist/{zh-D5rDYV6E.js → zh-DlXIP7ju.js} +17 -1
  49. package/dist/zh-DlXIP7ju.js.map +1 -0
  50. package/docs/firebase-setup.md +30 -2
  51. package/docs/firestore.rules.example +24 -8
  52. package/docs/roles-migration.md +32 -0
  53. package/docs/storage.rules.example +4 -4
  54. package/package.json +1 -1
  55. package/dist/ar-BCQdRw_S.js.map +0 -1
  56. package/dist/de-ddWQNaqs.js.map +0 -1
  57. package/dist/en-DxkqyZq2.js.map +0 -1
  58. package/dist/es-7JHjGVlM.js.map +0 -1
  59. package/dist/fr-C3NCMNoy.js.map +0 -1
  60. package/dist/id-C8hCAMPJ.js.map +0 -1
  61. package/dist/pt-B9EH4scA.js.map +0 -1
  62. package/dist/slugify-BuOMFLxN.js.map +0 -1
  63. package/dist/zh-D5rDYV6E.js.map +0 -1
package/README.md CHANGED
@@ -262,6 +262,7 @@ watch([categoryId, id], async ([category, slug]) => {
262
262
  - **로딩**: 언어별 번역 파일은 별도 청크로 그 언어를 쓸 때만 받는다(언어당 gzip 약 7KB). ko는 기본 번들에 들어 있다.
263
263
  - **날짜**: `Intl`로 그 언어 형식에 맞춰 표시한다. dayjs는 더 이상 쓰지 않는다.
264
264
  - **AI**: 검열·글쓰기 도우미 프롬프트도 보드 언어를 따른다(ko는 한국어 프롬프트, 그 외는 영어 프롬프트 + 해당 언어로 응답).
265
+ - **콘텐츠 번역**: 게시판 설정을 저장하면 이름·설명을, 섹션을 저장하면 제목을, 글쓰기 AI의 "10개 언어로 번역해 저장"은 글 제목·요약·본문을 10개 언어로 문서에 남긴다. 별도 컬렉션은 만들지 않는다. 화면 언어가 원문과 다르면 그 번역을 보여주고, 원문을 고치면 다시 번역할 때까지 원문을 보여 준다. 댓글은 작성한 언어 그대로다.
265
266
  - **아랍어**: 게시판 목록·상세·글쓰기 루트에 `dir="rtl"`이 붙는다.
266
267
 
267
268
  호스트에 `@nuxtjs/i18n`을 설치하면 사이트 언어 전환에 게시판이 자동으로 따라간다. locale `code`는 위 코드와 같게 둔다(`en-US`처럼 써도 앞 두 글자로 맞춘다).
@@ -325,7 +326,7 @@ a.memi-board-post-link:visited {
325
326
  ### 7. Security Rules + 인덱스 배포
326
327
 
327
328
  1. [docs/firestore.rules.example](docs/firestore.rules.example) → 호스트 `firestore.rules`에 병합
328
- (`memiBoardPosts`/`memiBoardComments`/`memiBoardLikes`/`memiBoardSettings`/`memiBoardUsers` — 전부 최상위 flat 컬렉션)
329
+ (`memiBoardPosts`/`memiBoardComments`/`memiBoardLikes`/`memiBoardReports`/`memiBoardSettings`/`memiBoardUsers` — 전부 최상위 flat 컬렉션)
329
330
  2. [docs/storage.rules.example](docs/storage.rules.example) → `storage.rules` 병합
330
331
  3. `firebase.json`의 Storage `bucket`이 `vuefire.config.storageBucket`과 같은지 확인
331
332
  (`*.appspot.com` vs `*.firebasestorage.app` — [firebase-setup.md](docs/firebase-setup.md) 참고)
@@ -336,6 +337,8 @@ a.memi-board-post-link:visited {
336
337
  ```
337
338
  6. Storage의 Firestore 조회 권한 설정: 배포 시 교차 서비스 IAM 역할 추가 안내를 승인하고, 호스트 Firebase 프로젝트의 Storage 서비스 계정에 `roles/firebaserules.firestoreServiceAgent`가 있는지 확인한다. [부모/호스트 설정 절차](docs/firebase-setup.md#storage-규칙의-firestore-조회-권한-부모호스트-프로젝트-필수-설정) 참고. CI 배포 성공만으로 이 권한 설정을 보장하지 않는다.
338
339
 
340
+ 0.45.1로 갱신하는 호스트는 [새 규칙 적용·전환 절차](docs/firebase-setup.md#0451-규칙-변경과-기존-호스트-전환)를 함께 확인한다. 패키지 설치나 사이트 푸시만으로 Firebase 규칙은 배포되지 않는다.
341
+
339
342
  **Rules를 파일에만 넣고 배포 안 하면** 글쓰기에서 `permission-denied` — 가장 흔한 설치 실수.
340
343
  **Storage 규칙을 앱이 쓰는 버킷이 아닌 곳에 배포하면** 본문 이미지에서 `storage/unauthorized`.
341
344
 
@@ -468,7 +471,7 @@ const router = useRouter()
468
471
  **에디터 이미지:** 툴바에서 기기 파일 또는 외부 HTTPS 이미지 URL을 추가할 수 있고, 붙여넣기 / 드롭 파일은 Firebase Storage에 업로드한다. 외부 URL은 Storage에 복사하지 않으며 원본이 삭제되거나 핫링크가 차단되면 표시되지 않는다. 첫 본문 이미지는 업로드·외부 링크 구분 없이 목록과 공유 대표 이미지로 사용한다.
469
472
 
470
473
  `memiBoardPosts/{postId}/images/*` + `.../images/thumbnails/*` (최적화 이미지+썸네일).
471
- 글 문서는 아직 없을 수 있고, 붙여넣기는 `image/png` 등이 온다. Storage 예시 규칙은 쓰기를 **로그인 + 5MB + `image/*`** 로 본다. 규칙은 **앱 `storageBucket`과 같은 버킷**에 배포해야 한다.
474
+ 글 문서는 아직 없을 수 있고, 붙여넣기는 `image/png` 등이 온다. Storage 예시 규칙은 쓰기를 **로그인 + 5MiB 미만 + `image/*` + 소유권 검증**으로 제한한다. 기존 글은 작성자 또는 전역 admin·staff만 수정할 수 있다. 글 저장 전에는 파일의 `customMetadata.ownerUid`가 로그인 UID와 같아야 하며, 기존 파일을 덮어쓸 때는 기존 메타데이터도 같아야 한다. 이미지 업로드도 Firestore의 글 존재 여부·소유권을 조회한다. 규칙은 **앱 `storageBucket`과 같은 버킷**에 배포해야 한다.
472
475
 
473
476
  본문 이미지는 휴대폰 원본 기준 최대 25MB까지 선택할 수 있다. 5MB를 넘으면 브라우저에서 최대 2560px·JPEG 85%로 최적화하고, 결과가 여전히 5MB를 넘으면 2048px·75%로 한 번 더 줄인 뒤 업로드한다. 5MB 이하 이미지는 원본을 유지한다.
474
477
  본문 HTML에는 원본 URL. 수정 중 버려진 파일은 호스트 스케줄러로 정리 (`extractEditorImageUrls` 로 본문 참조 비교).
@@ -514,8 +517,14 @@ const router = useRouter()
514
517
  | 댓글 작성 | 로그인 + 보드 `commentWriteRole` 충족. **숨김 보드는 담당 스태프만** (`commentWriteRole` 무시) |
515
518
  | 글 수정·삭제 | 작성자 또는 해당 보드 관리자·담당 스태프 |
516
519
  | 댓글 삭제 | 댓글 작성자 · 글 작성자 · 해당 보드 관리자·담당 스태프 |
520
+ | 연관 좋아요 정리 | 좋아요 작성자 또는 부모 글 작성자·해당 보드 관리자·담당 스태프. 댓글 좋아요는 댓글 작성자도 정리 가능 |
521
+ | 파일 업로드·삭제 | 기존 글은 글 작성자 또는 전역 admin·staff. 글 문서가 없으면 파일의 `ownerUid`로 검증 |
517
522
  | 역할 관리 | `MemiBoardUsers`에서 admin이 `admin`·`staff`·`user` 변경 |
518
523
 
524
+ 글 수정 시 `boardId`·`authorUid`는 바꿀 수 없고 `listed`는 공개 범위 전환 규칙을 따른다. 공개 범위 변경은 `visibilityTransition`을 기록해 새 글·댓글 생성과 일반 글 수정을 잠근 뒤 완료한다. 실패 시 같은 목표 공개 범위로 다시 저장한다. 글과 본문을 한 batch에서 만들 수 있도록 본문 쓰기는 `getAfter()`로 부모 글을 검증한다. 글 삭제 후에도 신고 기록은 보존한다.
525
+
526
+ Storage 예제의 파일 읽기·목록 조회는 공개다. 숨김 보드·비공개 초안의 Firestore 읽기 제한이 첨부파일에도 적용되는 것은 아니다. Storage의 admin·staff 권한은 보드 담당 여부와 무관한 전역 권한이다. 상세한 이유와 전환 방법은 [규칙 변경 안내](docs/firebase-setup.md#0451-규칙-변경과-기존-호스트-전환)를 참고한다.
527
+
519
528
  **알려진 한계:** AI 검열은 클라이언트 전용. SDK로 직접 write 하면 우회 가능. 중요 서비스면 Cloud Functions 검증을 호스트가 추가(패키지 범위 밖).
520
529
 
521
530
  ## Firebase AI Logic (검열)
@@ -621,13 +630,14 @@ App Check 토큰, `appId` 일치, 모델명(3.x), AI Logic 활성화, 네트워
621
630
  1. 로그인 상태인지 확인
622
631
  2. `vuefire.config.storageBucket`과 `firebase.json` `storage.bucket`이 **같은 이름**인지 확인. 구 프로젝트는 `*.appspot.com`(앱)과 `*.firebasestorage.app`(CLI 기본)이 갈라질 수 있다 — 앱이 쓰는 쪽에 규칙을 배포한다.
623
632
  3. 호스트 `storage.rules`에 `docs/storage.rules.example`의 `memiBoardPosts/{postId}/images` 규칙이 있는지 확인. 쓰기는 `image/*` 이어야 한다(붙여넣기 PNG).
624
- 4. `firebase deploy --only storage` 후 다시 올려 본다.
633
+ 4. 저장 전 파일에 `customMetadata.ownerUid`가 있는지 확인한다. 0.45.1 이상 패키지는 이를 자동으로 넣는다. 기존 글은 작성자·전역 admin·staff인지 확인한다.
634
+ 5. 이미지 규칙도 Firestore를 조회하므로 [교차 서비스 IAM 역할](docs/firebase-setup.md#storage-규칙의-firestore-조회-권한-부모호스트-프로젝트-필수-설정)을 확인하고, `firebase deploy --only storage` 후 다시 올려 본다.
625
635
 
626
636
  ### ZIP 등 첨부파일 `storage/unauthorized`
627
637
 
628
638
  `memiBoardPosts/{postId}/attachments/...`에서 실패하면 로그인 상태, 앱이 쓰는 버킷에 배포된 첨부 규칙, **파일당 20MiB 미만** 조건을 먼저 확인한다. 예시 규칙은 ZIP의 MIME 형식이나 `.exe.zip` 이름을 차단하지 않는다.
629
639
 
630
- 조건을 충족해도 실패하면 **호스트 Firebase 프로젝트의 Storage 서비스 계정에 Firestore 조회 역할이 있는지** 확인한다. 첨부 규칙은 `postExists`·`isPostOwner`·`isBoardStaff`에서 Firestore를 조회한다. 조회 권한이 없으면 새 글의 문서 존재 여부 확인부터 실패할 수 있다. [부모/호스트 설정 절차](docs/firebase-setup.md#storage-규칙의-firestore-조회-권한-부모호스트-프로젝트-필수-설정)에 따라 `roles/firebaserules.firestoreServiceAgent`를 설정한다.
640
+ 조건을 충족해도 실패하면 **호스트 Firebase 프로젝트의 Storage 서비스 계정에 Firestore 조회 역할이 있는지** 확인한다. 이미지·첨부 규칙은 `canWritePostFile`에서 `postExists`·`isPostOwner`·`isBoardStaff`를 통해 Firestore를 조회한다. 조회 권한이 없으면 새 글의 문서 존재 여부 확인부터 실패할 수 있다. [부모/호스트 설정 절차](docs/firebase-setup.md#storage-규칙의-firestore-조회-권한-부모호스트-프로젝트-필수-설정)에 따라 `roles/firebaserules.firestoreServiceAgent`를 설정한다.
631
641
 
632
642
  ### 글 삭제 `storage/unauthorized`
633
643
 
@@ -638,11 +648,12 @@ App Check 토큰, `appId` 일치, 모델명(3.x), AI Logic 활성화, 네트워
638
648
  ```
639
649
  match /memiBoardPosts/{postId}/{allPaths=**} {
640
650
  allow list, get: if true;
641
- allow delete: if isSignedIn()
642
- && (!postExists(postId) || isPostOwner(postId) || isBoardStaff());
651
+ allow delete: if canDeletePostFile(postId);
643
652
  }
644
653
  ```
645
654
 
655
+ `canDeletePostFile`과 관련 함수도 최신 `docs/storage.rules.example`에서 함께 병합한다. 글 문서가 없으면 로그인만으로 삭제를 허용하던 예전 조건(`!postExists`)은 사용하지 않는다.
656
+
646
657
  `write` 는 이 블록에 넣지 않는다(용량·`image/*` 제한은 `images`/`attachments` match 가 담당). `firebase deploy --only storage` 후 다시 삭제한다. 0.38.1+ 패키지는 `images/`·`attachments/`만 목록으로 비운다.
647
658
 
648
659
  목록 조회는 성공하지만 **개별 파일 삭제**가 거부되면, 위 [첨부파일 오류](#zip-등-첨부파일-storageunauthorized)와 같은 Firestore 조회 IAM 역할도 확인한다. 삭제 규칙 역시 글 소유권·사용자 역할을 Firestore에서 읽는다.
@@ -718,3 +729,7 @@ npm 패키지 설정의 Trusted Publisher는 GitHub 저장소 `fkkmemi/memi-boar
718
729
  - UI: `<MemiBoardVersionHistory />`
719
730
  - 상세: [CHANGELOG.md](CHANGELOG.md)
720
731
  - **MIT**
732
+
733
+ ### 0.46 역할 및 서비스별 명칭
734
+
735
+ 5개 역할과 게시판별 매니저 범위, `roleLabels`, 호스트 마이그레이션 절차는 [역할 마이그레이션](docs/roles-migration.md)을 참고한다. 설정 접근에는 `canConfigureBoard()`를 사용한다.
@@ -246,6 +246,10 @@ var e = {
246
246
  summarize: "تلخيص موجز",
247
247
  translateEn: "الترجمة إلى الإنجليزية",
248
248
  translateKo: "الترجمة إلى الكورية",
249
+ translateAll: "ترجمة وحفظ بـ 10 لغات",
250
+ translateSaved: "تم الحفظ بـ 10 لغات. يرى القارئ ترجمة لغته.",
251
+ translatePending: "ترجمات 10 لغات جاهزة. تُحفظ مع المنشور.",
252
+ translateEmpty: "أدخل عنوانًا أو نصًا للترجمة أولًا.",
249
253
  title: "إنشاء عنوان من النص",
250
254
  custom: "إعادة الصياغة بأسلوب مخصص…",
251
255
  failed: "فشلت مهمة AI. يُرجى المحاولة مرة أخرى."
@@ -529,6 +533,11 @@ var e = {
529
533
  label: "لغة اللوحة",
530
534
  auto: "تلقائي (حسب لغة الموقع)"
531
535
  },
536
+ autoTranslate: {
537
+ help: "عند الحفظ يُترجم الاسم والوصف إلى 10 لغات. لا يُعاد ترجمة النص إذا لم يتغير.",
538
+ failed: "حُفظت الإعدادات، لكن فشلت الترجمة إلى 10 لغات. احفظ مرة أخرى.",
539
+ ready: "الاسم والوصف محفوظان بـ 10 لغات."
540
+ },
532
541
  action: {
533
542
  moveDown: "للأسفل",
534
543
  goToBoard: "الانتقال إلى اللوحة",
@@ -603,6 +612,9 @@ var e = {
603
612
  contentRequired: "أدخل أولًا محتوى ليعمل عليه AI.",
604
613
  customRequired: "أدخل تعليمات لطريقة إعادة الصياغة.",
605
614
  emptyResult: "لم يتمكن AI من إنتاج نتيجة. يُرجى المحاولة مرة أخرى.",
615
+ translateEmpty: "أدخل عنوانًا أو نصًا للترجمة أولًا.",
616
+ translateTooLong: "لا يمكن حفظ نص أطول من {limit} حرفًا بـ 10 لغات. اختصره ثم أعد المحاولة.",
617
+ translateIncomplete: "عادت بعض اللغات فارغة. يُرجى المحاولة مرة أخرى.",
606
618
  failed: "فشلت مهمة AI. يُرجى المحاولة مرة أخرى.",
607
619
  actions: {
608
620
  proofread: "تدقيق الإملاء",
@@ -611,6 +623,10 @@ var e = {
611
623
  summarize: "تلخيص",
612
624
  translateEn: "الترجمة إلى الإنجليزية",
613
625
  translateKo: "الترجمة إلى الكورية",
626
+ translateAll: "ترجمة وحفظ بـ 10 لغات",
627
+ translateSaved: "تم الحفظ بـ 10 لغات. يرى القارئ ترجمة لغته.",
628
+ translatePending: "ترجمات 10 لغات جاهزة. تُحفظ مع المنشور.",
629
+ translateEmpty: "أدخل عنوانًا أو نصًا للترجمة أولًا.",
614
630
  title: "إنشاء عنوان من النص",
615
631
  custom: "إعادة الصياغة بأسلوب مخصص…"
616
632
  }
@@ -619,4 +635,4 @@ var e = {
619
635
  //#endregion
620
636
  export { e as default };
621
637
 
622
- //# sourceMappingURL=ar-BCQdRw_S.js.map
638
+ //# sourceMappingURL=ar-2vagHrlh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ar-2vagHrlh.js","names":[],"sources":["../src/i18n/messages/ar.ts"],"sourcesContent":["import type { MemiBoardMessages } from '../translate'\n\n/** 짧은 화면 문구만 번역한다. 빠진 키는 en → ko 순으로 폴백된다. */\nconst messages: MemiBoardMessages = {\n attachments: {\n attach: 'إرفاق ملفات ({count} / {max})',\n maxFiles: 'يمكنك إرفاق {max} ملفات كحد أقصى.',\n },\n auth: {\n writeRestricted: 'تم تقييد النشر والتعليق مؤقتًا بسبب تكرار تحذيرات المحتوى.',\n strikeSuffix: ' (تحذير {count}/{threshold})',\n strikeRestricted: ' عند بلوغ {count} تحذيرات سيتم تقييد النشر والتعليق.',\n notInitialized: '[memi-board] لم تتم تهيئة Firebase Auth.',\n },\n authorMemo: {\n tooLong: 'يمكن أن تصل الملاحظة إلى {max} حرف كحد أقصى.',\n },\n authorMenu: {\n viewProfile: 'عرض الملف الشخصي',\n viewPosts: 'عرض المنشورات',\n viewComments: 'عرض التعليقات',\n memoAriaLabel: 'ملاحظة عن هذا الكاتب',\n memoTitle: 'ملاحظة خاصة',\n memoPlaceholder: 'دوِّن شيئًا عن هذا الكاتب…',\n bad: 'سيئ',\n good: 'جيد',\n },\n avatarCropModal: {\n title: 'قص صورة الملف الشخصي',\n description: 'تُعرض بشكل مربع (1:1). اضبط المنطقة لتناسبها.',\n confirm: 'قص ورفع',\n },\n boardSettings: {\n signInRequired: 'يُرجى تسجيل الدخول.',\n idAndNameRequired: 'يُرجى إدخال معرّف اللوحة واسمها.',\n deleteIdRequired: 'يلزم معرّف اللوحة لحذفها.',\n nameRequired: 'يُرجى إدخال اسم اللوحة.',\n },\n boardUsers: {\n unsupportedRole: 'دور غير مدعوم.',\n roleDescription: {\n admin: 'يدير أدوار المستخدمين وإعدادات اللوحة وكل المحتوى.',\n user: 'يمكنه كتابة المنشورات والتعليقات، وتعديل أو حذف محتواه فقط.',\n },\n },\n commentFeed: {\n empty: 'لا توجد تعليقات بعد.',\n emptyLiked: 'لا توجد تعليقات حصلت على إعجاب بعد.',\n loadFailed: 'تعذّر تحميل القائمة: {error}',\n blinded: 'أخفى المشرف هذا التعليق.',\n },\n commentForm: {\n hiddenBoardDenied: 'في اللوحات المخفية، يمكن لفريق العمل المعيَّن فقط التعليق.',\n denied: 'ليس لديك إذن بالتعليق في هذه اللوحة.',\n signInRequired: 'يُرجى تسجيل الدخول.',\n writeRestricted: 'تم تقييد النشر مؤقتًا بسبب تكرار تحذيرات المحتوى.',\n moderationBlocked: 'يحتوي هذا على محتوى لا يمكن نشره.',\n replyTo: 'الرد على {name}',\n placeholder: 'اكتب تعليقًا',\n submitReply: 'رد',\n submit: 'تعليق',\n signInToComment: 'سجّل الدخول لكتابة تعليق.',\n },\n commentItem: {\n editRestricted: 'تم تقييد تعديل التعليقات مؤقتًا.',\n moderationBlocked: 'يحتوي هذا على محتوى لا يمكن نشره.',\n editFailed: 'تعذّر تعديل التعليق.',\n blindFailed: 'تعذّر تغيير حالة الإخفاء.',\n likeFailed: 'تعذّرت معالجة الإعجاب.',\n blinded: 'أخفى المشرف هذا التعليق.',\n edit: 'تعديل التعليق',\n blind: 'إخفاء التعليق',\n unblind: 'إظهار التعليق',\n delete: 'حذف التعليق',\n },\n commentList: {\n loading: 'جارٍ تحميل التعليقات',\n empty: 'لا توجد تعليقات بعد.',\n loadMore: 'المزيد من التعليقات',\n loadingMore: 'جارٍ تحميل المزيد من التعليقات',\n },\n commentThread: {\n deleteConfirm: 'هل تريد حذف هذا التعليق؟',\n deleteFailed: 'تعذّر حذف التعليق.',\n hideReplies: 'إخفاء الردود',\n showReplies: 'الردود ({count})',\n loadMoreReplies: 'المزيد من الردود',\n },\n comments: {\n bodyRequired: 'يُرجى إدخال تعليق.',\n bodyTooLong: 'يمكن أن يصل التعليق إلى {max} حرف كحد أقصى.',\n replyTargetNotFound: 'تعذّر العثور على التعليق الذي تردّ عليه.',\n threadNotFound: 'تعذّر العثور على سلسلة التعليقات.',\n hasRepliesCannotDelete: 'لا يمكن حذف التعليقات التي لها ردود حاليًا.',\n },\n common: {\n action: {\n cancel: 'إلغاء',\n save: 'حفظ',\n saved: 'تم الحفظ',\n edit: 'تعديل',\n delete: 'حذف',\n confirm: 'موافق',\n close: 'إغلاق',\n add: 'إضافة',\n more: 'المزيد',\n prev: 'السابق',\n next: 'التالي',\n retry: 'إعادة المحاولة',\n reply: 'رد',\n like: 'إعجاب',\n unlike: 'إلغاء الإعجاب',\n share: 'مشاركة',\n report: 'إبلاغ',\n signIn: 'تسجيل الدخول',\n signOut: 'تسجيل الخروج',\n write: 'كتابة',\n toTop: 'للأعلى',\n },\n label: {\n anonymous: 'مجهول',\n user: 'مستخدم',\n image: 'صورة',\n draft: 'مسودة',\n loading: 'جارٍ التحميل…',\n },\n role: {\n admin: 'مشرف',\n staff: 'فريق العمل',\n user: 'مستخدم',\n },\n date: {\n justNow: 'الآن',\n fewSecondsAgo: 'قبل بضع ثوانٍ',\n checking: 'جارٍ التحقق من الوقت',\n createdAt: 'تاريخ الإنشاء: {time}',\n updatedAt: 'تاريخ التعديل: {time}',\n },\n },\n detail: {\n deleteConfirm: 'هل تريد حذف هذا المنشور؟ لا يمكن التراجع عن ذلك.',\n notFound: 'لم يتم العثور على المنشور.',\n draftPreview: 'معاينة غير منشورة',\n draftNotice: 'لم يُنشر هذا المنشور بعد. لا يمكن لأحد غيرك رؤيته حتى تنشره.',\n publish: 'نشر',\n backToList: 'العودة إلى القائمة',\n postNav: 'التنقل بين المنشورات',\n list: 'القائمة',\n comments: 'التعليقات',\n },\n editor: {\n titlePlaceholder: 'العنوان',\n contentPlaceholder: 'اكتب شيئًا… اكتب / لفتح قائمة الكتل.',\n imageContentPlaceholder: 'اكتب شيئًا…',\n tagsPlaceholder: 'اكتب وسمًا واضغط Enter',\n skipModeration: 'تجاوز فلتر الألفاظ النابية (للمشرف)',\n submitEdit: 'حفظ التغييرات',\n submitNext: 'التالي: معاينة',\n apply: 'تطبيق',\n imageOptimizing: 'جارٍ تحسين الصورة ورفعها…',\n cover: {\n imageLink: 'رابط الصورة',\n addPhoto: 'إضافة صور',\n alt: 'صورة {n}',\n representative: 'الغلاف',\n setRepresentative: 'تعيين كغلاف',\n setRepresentativeAria: 'تعيين الصورة {n} كغلاف',\n remove: 'إزالة الصورة',\n add: 'إضافة',\n uploading: 'جارٍ الرفع…',\n dropOrClick: 'أفلت الصور هنا أو انقر للإضافة',\n emptyHint: 'يمكن اختيار عدة صور · اختر الغلاف بعد الرفع · اسحب لإعادة الترتيب',\n uploadTitle: 'رفع',\n },\n uploadFailed: 'فشل الرفع',\n aiAssistant: 'مساعد الكتابة بالذكاء الاصطناعي',\n insertBlock: 'إدراج كتلة',\n blockMenu: 'قائمة الكتل',\n imageDialog: {\n title: 'إضافة صورة',\n chooseFile: 'اختيار صورة من جهازك',\n orLink: 'أو رابط صورة',\n urlLabel: 'URL الصورة',\n previewAlt: 'معاينة صورة خارجية',\n addLink: 'إضافة الرابط',\n },\n customDialog: {\n title: 'إعادة الصياغة بأسلوب مخصص',\n label: 'كيف تريد إعادة صياغته؟',\n help: 'مثال: بأسلوب ودّي / بأسلوب رسمي / كنقاط موجزة',\n placeholder: 'بأسلوب ودّي',\n },\n aiPreview: {\n title: 'مراجعة نتيجة AI',\n currentTitle: 'العنوان الحالي',\n newTitle: 'العنوان الجديد',\n currentContent: 'المحتوى الحالي',\n appendedContent: 'المحتوى المضاف',\n before: 'قبل',\n after: 'بعد',\n },\n menu: {\n ai: 'AI',\n aiContinue: 'المتابعة باستخدام AI',\n style: 'النمط',\n insert: 'إدراج',\n paragraph: 'فقرة',\n heading1: 'عنوان 1',\n heading2: 'عنوان 2',\n heading3: 'عنوان 3',\n bulletList: 'قائمة نقطية',\n orderedList: 'قائمة مرقّمة',\n blockquote: 'اقتباس',\n codeBlock: 'كتلة برمجية',\n horizontalRule: 'فاصل',\n image: 'صورة',\n turnInto: 'تحويل إلى',\n resetFormatting: 'إعادة تعيين التنسيق',\n duplicate: 'تكرار',\n moveUp: 'نقل للأعلى',\n moveDown: 'نقل للأسفل',\n delete: 'حذف',\n },\n toolbar: {\n link: 'رابط',\n image: 'صورة',\n format: 'التنسيق',\n bold: 'غامق',\n italic: 'مائل',\n underline: 'تسطير',\n strike: 'يتوسطه خط',\n title: 'العنوان',\n heading: 'عنوان',\n subheading: 'عنوان فرعي',\n body: 'النص',\n mono: 'أحادي المسافة',\n bulletList: 'قائمة نقطية',\n orderedList: 'قائمة مرقّمة',\n blockquote: 'اقتباس',\n codeBlock: 'كتلة برمجية',\n horizontalRule: 'فاصل',\n clearFormatting: 'مسح التنسيق',\n undo: 'تراجع',\n redo: 'إعادة',\n },\n ai: {\n proofread: 'تدقيق الإملاء',\n polish: 'تحسين الصياغة',\n continue: 'متابعة الكتابة',\n summarize: 'تلخيص موجز',\n translateEn: 'الترجمة إلى الإنجليزية',\n translateKo: 'الترجمة إلى الكورية',\n translateAll: 'ترجمة وحفظ بـ 10 لغات',\n translateSaved: 'تم الحفظ بـ 10 لغات. يرى القارئ ترجمة لغته.',\n translatePending: 'ترجمات 10 لغات جاهزة. تُحفظ مع المنشور.',\n translateEmpty: 'أدخل عنوانًا أو نصًا للترجمة أولًا.',\n title: 'إنشاء عنوان من النص',\n custom: 'إعادة الصياغة بأسلوب مخصص…',\n failed: 'فشلت مهمة AI. يُرجى المحاولة مرة أخرى.',\n },\n youtubePrompt: 'أدخل رابط YouTube.',\n errors: {\n coverMax: 'يمكنك رفع {max} صور كحد أقصى.',\n imageUrlRequired: 'أدخل URL الصورة.',\n imageUrlInvalid: 'أدخل URL صالحًا للصورة.',\n imageUrlHttps: 'يُسمح فقط بعناوين URL للصور التي تبدأ بـ HTTPS.',\n imageUrlCredentials: 'لا يُسمح بعناوين URL التي تحتوي على بيانات تسجيل الدخول.',\n imageTimeout: 'يستغرق تحميل الصورة وقتًا طويلًا جدًا.',\n imageNotDisplayable: 'لا يمكن عرض هذه الصورة.',\n editorNotReady: 'المحرر غير جاهز. يُرجى المحاولة مرة أخرى.',\n imageLinkFailed: 'تعذّرت إضافة رابط الصورة.',\n imageOnly: 'يمكن رفع ملفات الصور فقط.',\n imageTooLarge: 'يجب ألا يتجاوز حجم الصورة الأصلية {mb}MB.',\n imageUploadFailed: 'فشل رفع الصورة.',\n pasteAsFile: 'الصق الصور كملفات أو اسحبها إلى المنطقة أعلاه.',\n postNotFound: 'لم يتم العثور على المنشور.',\n loadPermission: 'ليس لديك إذن بتحميل هذا المنشور. تأكد من تسجيل الدخول.',\n loadFailed: 'تعذّر تحميل المنشور: {message}',\n titleRequired: 'أدخل عنوانًا.',\n photoRequired: 'ارفع صورة واحدة على الأقل.',\n bodyRequired: 'اكتب نصًا أو أرفق صورة.',\n signInRequired: 'يلزم تسجيل الدخول.',\n boardMissing: 'لم يتم تحديد لوحة.',\n writeRestricted: 'تم تقييد النشر والتعليق مؤقتًا بسبب تكرار تحذيرات المحتوى.',\n moderationBlocked: 'يحتوي هذا على محتوى لا يمكن نشره.',\n },\n hint: {\n reviewing: 'جارٍ مراجعة المحتوى…',\n saving: 'جارٍ الحفظ…',\n },\n },\n footer: {\n info: {\n title: 'معلومات اللوحة',\n about: 'عن هذه اللوحة',\n history: 'سجل الإصدارات',\n },\n stack: {\n ui: 'الواجهة والتجربة',\n data: 'البيانات وتسجيل الدخول',\n writing: 'الكتابة والأدوات المساعدة',\n },\n },\n likes: {\n signInRequired: 'يُرجى تسجيل الدخول.',\n commentNotFound: 'لم يتم العثور على التعليق.',\n },\n list: {\n settings: 'الإعدادات',\n loadFailed: 'تعذّر تحميل القائمة: {error}',\n empty: 'لا توجد منشورات بعد.',\n photo: 'صورة',\n likeCount: 'الإعجابات {count}',\n info: {\n title: 'معلومات اللوحة',\n about: 'عن هذه اللوحة',\n history: 'سجل الإصدارات',\n },\n stack: {\n framework: 'إطار العمل واللغة',\n ui: 'الواجهة والتنسيق',\n backend: 'الخلفية والبيانات',\n },\n tech: {\n nuxt: 'إطار عمل SSR مبني على Vue',\n vue: 'Composition API · script setup',\n typescript: 'أنواع آمنة للبيانات والمكونات',\n nuxtUi: 'واجهة اللوحة والنماذج والنوافذ المنبثقة',\n tailwind: 'متجاوب · أنماط فاتحة/داكنة',\n lucide: 'أيقونات خفيفة ومتناسقة',\n dayjs: 'تواريخ وأوقات نسبية سهلة القراءة',\n firestore: 'قاعدة بيانات فورية للمنشورات والتعليقات',\n auth: 'تسجيل دخول وأذونات آمنة',\n storage: 'تخزين الصور والمرفقات',\n vuefire: 'ربط فوري بين Nuxt وFirebase',\n },\n },\n listViewSwitch: {\n default: 'افتراضي',\n dense: 'مضغوط',\n video: 'فيديو',\n },\n miniComments: {\n empty: 'لا توجد تعليقات بعد.',\n blinded: 'أخفى المشرف هذا التعليق.',\n },\n miniList: {\n empty: 'لا توجد منشورات بعد.',\n },\n miniPicker: {\n selectedCount: 'المحدد {count} / {max}',\n layoutHint: 'من 2 فأكثر تُعرض كشريط دوّار، و4 كشبكة',\n searchPlaceholder: 'البحث في العناوين',\n loading: 'جارٍ تحميل المنشورات…',\n noMore: 'لا توجد منشورات أخرى للاختيار.',\n },\n miniPost: {\n empty: 'لم يتم اختيار منشورات.',\n },\n moderation: {\n writeRestricted: 'تم تقييد النشر والتعليق مؤقتًا بسبب تكرار تحذيرات المحتوى.',\n },\n moderationPrompt: {\n localHit: 'لا يُسمح بتعبيرات مثل «{hit}». يُرجى إعادة الصياغة.',\n localGeneric: 'يحتوي هذا على ألفاظ نابية، لذا لا يمكن نشره. يُرجى إعادة الصياغة.',\n genericBlocked: 'يحتوي هذا على محتوى لا يمكن نشره. يُرجى تعديله.',\n categories: {\n abuse: 'ألفاظ نابية أو إهانات',\n spam: 'رسائل مزعجة أو إعلانات',\n adult: 'محتوى جنسي أو للبالغين',\n violence: 'عنف أو تهديدات',\n other: 'محتوى غير لائق',\n },\n },\n moderationStrike: {\n liftSoon: 'يُرفع قريبًا',\n liftInHours: 'يُرفع بعد {hours} ساعة تقريبًا',\n liftInDays: 'يُرفع بعد {days} يوم تقريبًا',\n liftInDaysHours: 'يُرفع بعد {days} يوم و{hours} ساعة تقريبًا',\n },\n posts: {\n boardIdRequired: '[memi-board] boardId مطلوب.',\n photoRequired: 'يُرجى إضافة صورة.',\n bodyRequired: 'يُرجى إدخال نص أو إرفاق صورة.',\n titleRequired: 'يُرجى إدخال عنوان.',\n },\n profile: {\n notFound: 'لم يتم العثور على المستخدم.',\n noName: 'بلا اسم',\n joined: 'انضم {time}',\n lastVisit: 'آخر ظهور {time}',\n },\n profileEditModal: {\n imageOnly: 'يمكن اختيار ملفات الصور فقط.',\n nameRequired: 'يُرجى إدخال اسم.',\n saveFailed: 'تعذّر حفظ الملف الشخصي.',\n title: 'تعديل الملف الشخصي',\n changePhoto: 'تغيير الصورة',\n name: 'الاسم',\n bio: 'نبذة',\n bioPlaceholder: 'عرّف الآخرين بنفسك',\n },\n reportButton: {\n failed: 'تعذّر إرسال البلاغ.',\n reported: 'تم الإبلاغ',\n title: 'الإبلاغ عن المنشور',\n done: 'تم استلام البلاغ. سيراجعه فريق العمل.',\n reason: 'السبب',\n detailPlaceholder: 'التفاصيل (اختياري)',\n submit: 'إبلاغ',\n },\n reportList: {\n staffOnly: 'يمكن لفريق العمل فقط عرض البلاغات.',\n loadFailed: 'تعذّر تحميل القائمة: {error}',\n empty: 'لا توجد بلاغات معلّقة.',\n untitled: 'منشور بلا عنوان',\n author: 'بواسطة {name}',\n dismiss: 'رفض',\n confirm: 'معالجة',\n },\n reports: {\n reason: {\n spam: 'رسائل مزعجة / إعلانات',\n abuse: 'إساءة / كراهية / تحرّش',\n adult: 'محتوى جنسي',\n illegal: 'محتوى غير قانوني',\n other: 'أخرى',\n },\n fallbackLabel: 'بلاغ',\n signInRequired: 'يُرجى تسجيل الدخول.',\n cannotReportOwn: 'لا يمكنك الإبلاغ عن منشورك.',\n reasonRequired: 'يُرجى اختيار سبب.',\n detailTooLong: 'يمكن أن تصل التفاصيل إلى {max} حرف كحد أقصى.',\n },\n sectionUtil: {\n kind: {\n list: 'قائمة المنشورات',\n post: 'منشورات',\n comments: 'تعليقات',\n },\n sort: {\n latest: 'الأحدث',\n likes: 'الأكثر إعجابًا',\n },\n untitledPost: 'منشور',\n view: {\n media: 'الوسائط',\n list: 'قائمة',\n },\n },\n sections: {\n defaultTitle: 'القسم {n}',\n },\n seo: {\n allCategory: 'الكل',\n boardDescription: 'لوحة {name}',\n allDescription: 'اللوحة — الإعلانات وأخبار المجتمع.',\n postFallbackTitle: 'منشور | {site}',\n postFallbackDescription: 'اللوحة',\n photo: 'صورة',\n postDescriptionFallback: 'منشور في اللوحة',\n listTitle: '{name} | {site}',\n listTitleAll: 'اللوحة | {site}',\n listDescription: '{name} — تابع أحدث المنشورات.',\n listDescriptionAll: 'اللوحة — إعلانات وقصص.',\n },\n settings: {\n loading: 'جارٍ تحميل الإعدادات…',\n noAccess: 'ليس لديك صلاحية الوصول',\n notFound: {\n title: 'لم يتم العثور على اللوحة',\n checkAddress: 'يُرجى التحقق من العنوان.',\n checkBoardAddress: 'يُرجى التحقق من عنوان اللوحة.',\n },\n newBoard: {\n title: 'هذه اللوحة غير موجودة بعد',\n description: 'املأ البيانات أدناه واحفظها لإنشائها بهذا المعرّف.',\n },\n noLabel: 'بلا تسمية',\n option: {\n default: 'قياسي',\n },\n id: {\n label: 'معرّف الفئة',\n help: 'يُستخدم في URL والبيانات',\n },\n writeRole: {\n label: 'من يمكنه النشر',\n help: 'الحد الأدنى من الدور المطلوب للنشر في هذه الفئة',\n helpShort: 'الحد الأدنى من الدور المطلوب للنشر',\n options: {\n user: 'المستخدمون فما فوق',\n staff: 'فريق العمل فما فوق',\n admin: 'المشرفون فقط',\n },\n },\n commentWriteRole: {\n label: 'من يمكنه التعليق',\n help: 'الحد الأدنى من الدور المطلوب للتعليق في هذه الفئة',\n helpShort: 'الحد الأدنى من الدور المطلوب للتعليق',\n hiddenFixed: 'فريق العمل المعيَّن فقط (ثابت للوحات المخفية)',\n },\n staff: {\n label: 'فريق العمل المعيَّن',\n help: 'يمكن لفريق العمل المحدد فقط إعداد هذه اللوحة وإدارتها. اتركه فارغًا ليقتصر الأمر على المشرفين',\n placeholder: 'المشرفون فقط (دون تعيين فريق عمل)',\n },\n label: {\n label: 'الاسم المعروض',\n help: 'الاسم الذي يراه المستخدمون',\n },\n description: {\n label: 'الوصف',\n help: 'يظهر أعلى اللوحة وفي أماكن أخرى',\n placeholder: 'وصف قصير لهذه اللوحة (اختياري)',\n },\n visibility: {\n label: 'الظهور',\n options: {\n public: {\n label: 'ظاهرة',\n description: 'تظهر في القائمة الكاملة وعوامل التصفية',\n },\n hidden: {\n label: 'مخفية',\n description: 'يوميات أو خاصة. يمكن لفريق العمل المعيَّن فقط التعليق',\n },\n },\n },\n listView: {\n label: 'عرض القائمة',\n help: 'طريقة عرض قائمة المنشورات',\n options: {\n dense: 'مضغوط',\n video: 'فيديو',\n },\n },\n editorType: {\n label: 'نموذج النشر',\n help: 'طريقة كتابة المنشورات',\n options: {\n default: 'كتابة عنوان ونص',\n image: 'إضافة عدة صور وصورة غلاف',\n },\n },\n locale: {\n label: 'لغة اللوحة',\n auto: 'تلقائي (حسب لغة الموقع)',\n },\n autoTranslate: {\n help: 'عند الحفظ يُترجم الاسم والوصف إلى 10 لغات. لا يُعاد ترجمة النص إذا لم يتغير.',\n failed: 'حُفظت الإعدادات، لكن فشلت الترجمة إلى 10 لغات. احفظ مرة أخرى.',\n ready: 'الاسم والوصف محفوظان بـ 10 لغات.',\n },\n action: {\n moveDown: 'للأسفل',\n goToBoard: 'الانتقال إلى اللوحة',\n deleteBoard: 'حذف اللوحة',\n create: 'إنشاء',\n },\n newCategoryPlaceholder: 'اسم الفئة الجديدة',\n savedCategory: 'تم حفظ الفئة «{label}».',\n savedSettings: 'تم حفظ إعدادات اللوحة.',\n created: 'تم إنشاء اللوحة.',\n error: {\n orderFailed: 'تعذّر حفظ ترتيب الفئات.',\n deleteFailed: 'تعذّر حذف اللوحة وبياناتها.',\n saveFailed: 'تعذّر حفظ إعدادات اللوحة.',\n deleteAllFailed: 'تعذّر حذف بيانات اللوحة.',\n },\n danger: {\n title: 'منطقة الخطر',\n confirmLabel: 'اكتب «{phrase}» للتأكيد',\n phrase: 'حذف بيانات اللوحة',\n button: 'حذف كل البيانات',\n done: 'تم حذف كل بيانات اللوحة.',\n },\n },\n shareButton: {\n copied: 'تم نسخ الرابط',\n },\n signIn: {\n withGoogle: 'تسجيل الدخول باستخدام Google',\n withApple: 'تسجيل الدخول باستخدام Apple',\n or: 'أو',\n name: 'الاسم',\n email: 'البريد الإلكتروني',\n password: 'كلمة المرور',\n signUp: 'إنشاء حساب',\n toSignUp: 'ليس لديك حساب؟ أنشئ حسابًا',\n toSignIn: 'لديك حساب بالفعل؟ سجّل الدخول',\n },\n storage: {\n sourceTooLarge: 'يجب ألا يتجاوز حجم الصورة الأصلية {max}MB.',\n imageOnly: 'يمكن رفع ملفات الصور فقط.',\n canvasUnavailable: 'Canvas غير متاح',\n compressFailed: 'فشل الضغط',\n loadFailed: 'فشل تحميل الصورة',\n },\n swipeHint: {\n title: 'جرّب السحب يمينًا أو يسارًا',\n description: 'اسحب لليسار للمنشور التالي، ولليمين للمنشور السابق.',\n },\n userCommentList: {\n loadFailed: 'تعذّر تحميل القائمة: {error}',\n empty: 'لا توجد تعليقات بعد.',\n blinded: 'أخفى المشرف هذا التعليق.',\n likes: 'الإعجابات {count}',\n },\n userPostList: {\n loadFailed: 'تعذّر تحميل القائمة: {error}',\n empty: 'لا توجد منشورات بعد.',\n },\n users: {\n changeRoleFailed: 'تعذّر تغيير الدور.',\n loading: 'جارٍ تحميل المستخدمين…',\n noPermission: {\n title: 'لا توجد صلاحية',\n description: 'يمكن لمشرفي اللوحة فقط إدارة أدوار المستخدمين.',\n },\n empty: 'لم يسجّل أي مستخدم الدخول إلى اللوحة بعد.',\n noName: 'بلا اسم',\n moderationWarnings: 'تحذيرات المحتوى: {count}',\n },\n writingAssistant: {\n loginRequired: 'سجّل الدخول لاستخدام مساعد الكتابة بالذكاء الاصطناعي.',\n userMissing: 'تعذّر التحقق من معلومات المستخدم. يُرجى المحاولة مرة أخرى.',\n contentRequired: 'أدخل أولًا محتوى ليعمل عليه AI.',\n customRequired: 'أدخل تعليمات لطريقة إعادة الصياغة.',\n emptyResult: 'لم يتمكن AI من إنتاج نتيجة. يُرجى المحاولة مرة أخرى.',\n translateEmpty: 'أدخل عنوانًا أو نصًا للترجمة أولًا.',\n translateTooLong: 'لا يمكن حفظ نص أطول من {limit} حرفًا بـ 10 لغات. اختصره ثم أعد المحاولة.',\n translateIncomplete: 'عادت بعض اللغات فارغة. يُرجى المحاولة مرة أخرى.',\n failed: 'فشلت مهمة AI. يُرجى المحاولة مرة أخرى.',\n actions: {\n proofread: 'تدقيق الإملاء',\n polish: 'تحسين الجمل',\n continue: 'متابعة الكتابة',\n summarize: 'تلخيص',\n translateEn: 'الترجمة إلى الإنجليزية',\n translateKo: 'الترجمة إلى الكورية',\n translateAll: 'ترجمة وحفظ بـ 10 لغات',\n translateSaved: 'تم الحفظ بـ 10 لغات. يرى القارئ ترجمة لغته.',\n translatePending: 'ترجمات 10 لغات جاهزة. تُحفظ مع المنشور.',\n translateEmpty: 'أدخل عنوانًا أو نصًا للترجمة أولًا.',\n title: 'إنشاء عنوان من النص',\n custom: 'إعادة الصياغة بأسلوب مخصص…',\n },\n },\n}\n\nexport default messages\n"],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  type BoardLookup = (boardId: string) => {
2
2
  allowedStaffUids?: string[];
3
+ allowedManagerUids?: string[];
3
4
  } | undefined;
4
5
  export declare function registerBoardLookup(fn: BoardLookup): void;
5
6
  export declare function resolveBoardLookup(boardId: string): {
6
7
  allowedStaffUids?: string[];
8
+ allowedManagerUids?: string[];
7
9
  } | undefined;
8
10
  export {};
@@ -8,7 +8,7 @@ export type PublicSeoDb = {
8
8
  };
9
9
  export declare function resolvePublicSeoDb(): PublicSeoDb;
10
10
  /** 공개 글 1건 (boardId + slug) */
11
- export declare function fetchPublicPostForSeo(boardId: string, slug: string, ctx?: PublicSeoDb): Promise<BoardPostSeoPayload | null>;
11
+ export declare function fetchPublicPostForSeo(boardId: string, slug: string, ctx?: PublicSeoDb, locale?: MemiBoardLocale): Promise<BoardPostSeoPayload | null>;
12
12
  /** 전체 보드 목록 또는 단일 보드 SEO */
13
13
  export declare function fetchPublicListForSeo(boardId?: string | null, ctx?: PublicSeoDb,
14
14
  /** 기본 문구(설명·'전체' 라벨) 언어. 생략 시 한국어. */
@@ -1,10 +1,14 @@
1
1
  import { Ref, ComputedRef } from 'vue';
2
2
  import { useCurrentUser } from 'vuefire';
3
3
  import { signInWithPopup, signInWithEmailAndPassword, createUserWithEmailAndPassword } from 'firebase/auth';
4
- import { PostModel, CommentModel } from '../types';
4
+ import { PostModel, CommentModel, BoardUserRole } from '../types';
5
5
  export interface UseMemiBoardAuthReturn {
6
6
  user: ReturnType<typeof useCurrentUser>;
7
7
  isSignedIn: ComputedRef<boolean>;
8
+ role: Ref<BoardUserRole>;
9
+ isOwner: ComputedRef<boolean>;
10
+ isManager: ComputedRef<boolean>;
11
+ canConfigureBoard: (boardId: string | undefined | null) => boolean;
8
12
  isAdmin: ComputedRef<boolean>;
9
13
  isStaff: ComputedRef<boolean>;
10
14
  canManageContent: ComputedRef<boolean>;
@@ -1,4 +1,6 @@
1
1
  import { MemiBoardLocale } from '../i18n/locales';
2
+ import { MemiBoardI18nMap } from '../types';
3
+ import { localizedText, mergeSourceFields } from '../utils/localizedFields';
2
4
  /** 번역할 항목 하나. fields 의 키(label, description 등)별로 번역 결과가 돌아온다. */
3
5
  export interface MemiBoardTranslateEntry {
4
6
  id: string;
@@ -7,7 +9,7 @@ export interface MemiBoardTranslateEntry {
7
9
  context?: string;
8
10
  }
9
11
  /** 언어별 번역 결과. `{ en: { label: 'About' }, ja: { label: '…' } }` */
10
- export type MemiBoardLocalizedFields = Partial<Record<MemiBoardLocale, Record<string, string>>>;
12
+ export type MemiBoardLocalizedFields = MemiBoardI18nMap;
11
13
  export interface MemiBoardTranslateOptions {
12
14
  /** 원문 언어. 기본 'ko' */
13
15
  source?: MemiBoardLocale;
@@ -16,6 +18,7 @@ export interface MemiBoardTranslateOptions {
16
18
  /** 무엇을 번역하는지 한 줄 설명(예: 'website navigation menu labels'). 문체를 맞추는 데 쓴다. */
17
19
  purpose?: string;
18
20
  }
21
+ export declare const HTML_MAX_CHARS = 12000;
19
22
  /**
20
23
  * 짧은 UI 문구(메뉴 이름, 게시판 이름 등)를 Firebase AI Logic 으로 여러 언어로 번역한다.
21
24
  * 자동으로 저장하지 않는다 — 호출한 쪽이 결과를 보여 주고 저장 여부를 정한다.
@@ -25,9 +28,6 @@ export interface MemiBoardTranslateOptions {
25
28
  */
26
29
  export declare function useMemiBoardAutoTranslate(): {
27
30
  translateEntries: (entries: MemiBoardTranslateEntry[], options?: MemiBoardTranslateOptions) => Promise<Record<string, MemiBoardLocalizedFields>>;
31
+ translateHtml: (html: string, options?: MemiBoardTranslateOptions) => Promise<Partial<Record<MemiBoardLocale, string>>>;
28
32
  };
29
- /**
30
- * 저장된 번역에서 현재 언어 값을 고른다. 현재 언어 → en → 원문 순.
31
- * `localizedText(item.i18n, locale, 'label', item.label)`
32
- */
33
- export declare function localizedText(i18n: MemiBoardLocalizedFields | null | undefined, locale: string | null | undefined, field: string, fallback: string): string;
33
+ export { localizedText, mergeSourceFields };
@@ -1,6 +1,6 @@
1
1
  import { MaybeRefOrGetter, Ref } from 'vue';
2
2
  import { DocumentData, QueryDocumentSnapshot } from 'firebase/firestore';
3
- import { Attachment, PostDetail, PostModel } from '../types';
3
+ import { Attachment, PostDetail, PostModel, PostTranslationSet } from '../types';
4
4
  export interface CreatePostInput {
5
5
  postId?: string;
6
6
  title: string;
@@ -13,12 +13,16 @@ export interface CreatePostInput {
13
13
  moderationModel?: string;
14
14
  /** resolveUniqueSlug 로 미리 구한 slug. 없으면 createPost 가 직접 구한다. */
15
15
  slug?: string;
16
+ /** 10개 언어 번역. 있으면 글과 본문에 함께 저장한다. */
17
+ translations?: PostTranslationSet;
16
18
  }
17
19
  export interface UpdatePostInput {
18
20
  title: string;
19
21
  content: string;
20
22
  tags?: string[];
21
23
  attachments?: Attachment[];
24
+ /** 있으면 제목·본문 번역을 이 값으로 바꾼다. 없으면 기존 번역을 건드리지 않는다. */
25
+ translations?: PostTranslationSet;
22
26
  }
23
27
  export type GetPostBySlugResult = {
24
28
  status: 'ok';
@@ -65,6 +69,7 @@ export declare function useMemiBoardPosts(boardId: MaybeRefOrGetter<string>): {
65
69
  }>;
66
70
  createPost: (input: CreatePostInput) => Promise<string>;
67
71
  updatePost: (id: string, input: UpdatePostInput) => Promise<void>;
72
+ savePostTranslations: (id: string, translations: PostTranslationSet) => Promise<void>;
68
73
  publishPost: (id: string) => Promise<void>;
69
74
  deletePost: (id: string) => Promise<void>;
70
75
  };
@@ -11,8 +11,8 @@ export declare const DEFAULT_CATEGORIES: BoardModel[];
11
11
  export declare function useMemiBoardSettings(): {
12
12
  boards: import('vue').ComputedRef<BoardModel[]>;
13
13
  publicBoards: import('vue').ComputedRef<BoardModel[]>;
14
- boardLabel: (id: string | undefined) => string | undefined;
15
- boardDescription: (id: string | undefined) => string | undefined;
14
+ boardLabel: (id: string | undefined, localeOverride?: string | null) => string | undefined;
15
+ boardDescription: (id: string | undefined, localeOverride?: string | null) => string | undefined;
16
16
  boardVisibility: (id: string | undefined) => BoardVisibility;
17
17
  isBoardHidden: (id: string | undefined) => boolean;
18
18
  getBoard: (id: string | undefined) => BoardModel | undefined;
@@ -20,8 +20,8 @@ export declare function useMemiBoardSettings(): {
20
20
  managedBoards: import('vue').ComputedRef<BoardModel[]>;
21
21
  categories: import('vue').ComputedRef<BoardModel[]>;
22
22
  publicCategories: import('vue').ComputedRef<BoardModel[]>;
23
- categoryLabel: (id: string | undefined) => string | undefined;
24
- categoryDescription: (id: string | undefined) => string | undefined;
23
+ categoryLabel: (id: string | undefined, localeOverride?: string | null) => string | undefined;
24
+ categoryDescription: (id: string | undefined, localeOverride?: string | null) => string | undefined;
25
25
  categoryVisibility: (id: string | undefined) => BoardVisibility;
26
26
  isCategoryHidden: (id: string | undefined) => boolean;
27
27
  settingsPending: import('vue').Ref<boolean, boolean>;
@@ -30,6 +30,7 @@ export declare function useMemiBoardSettings(): {
30
30
  addCategory: (label: string) => Promise<string>;
31
31
  saveBoard: (board: BoardModel, order?: number) => Promise<void>;
32
32
  saveCategory: (board: BoardModel, order?: number) => Promise<void>;
33
+ translateBoard: (board: BoardModel) => Promise<BoardModel>;
33
34
  saveBoards: (next: BoardModel[]) => Promise<void>;
34
35
  saveCategories: (next: BoardModel[]) => Promise<void>;
35
36
  deleteBoard: (id: string) => Promise<void>;
@@ -1,4 +1,6 @@
1
1
  import { MaybeRefOrGetter } from 'vue';
2
+ import { MemiBoardLocale } from '../i18n/locales';
3
+ import { PostTranslationSet } from '../types';
2
4
  export declare const WRITING_ASSISTANT_DAILY_LIMIT = 10;
3
5
  export type WritingAssistantAction = 'proofread' | 'polish' | 'continue' | 'summarize' | 'translate-en' | 'translate-ko' | 'title' | 'custom';
4
6
  interface WritingAssistantInput {
@@ -19,5 +21,11 @@ export interface UseMemiBoardWritingAssistantOptions {
19
21
  /** 검열과 동일한 Firebase AI Logic 연결을 사용하는 글쓰기 도우미. */
20
22
  export declare function useMemiBoardWritingAssistant(options?: UseMemiBoardWritingAssistantOptions): {
21
23
  assist: (input: WritingAssistantInput) => Promise<string>;
24
+ translatePost: (input: {
25
+ source?: MemiBoardLocale;
26
+ title?: string;
27
+ summary?: string;
28
+ content?: string;
29
+ }) => Promise<PostTranslationSet>;
22
30
  };
23
31
  export {};
package/dist/config.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { BoardUserRole } from './types';
1
2
  import { BoardPathConfig } from './utils/boardPaths';
2
3
  export interface MemiBoardAuthOptions {
3
4
  providers?: Array<'google' | 'apple' | 'emailPassword'>;
@@ -43,6 +44,7 @@ export interface MemiBoardConfig {
43
44
  * 기본 언어. 호스트에 @nuxtjs/i18n 이 있으면 그 언어를 따르고,
44
45
  * 보드 설정에서 언어를 고르면 그 보드는 그 언어로 고정된다. 기본 'ko'.
45
46
  */
47
+ roleLabels?: Partial<Record<BoardUserRole, string>>;
46
48
  locale?: string;
47
49
  }
48
50
  export declare function configureMemiBoard(options: Partial<MemiBoardConfig>): void;
@@ -246,6 +246,10 @@ var e = {
246
246
  summarize: "Kurz zusammenfassen",
247
247
  translateEn: "Ins Englische übersetzen",
248
248
  translateKo: "Ins Koreanische übersetzen",
249
+ translateAll: "In 10 Sprachen übersetzen und speichern",
250
+ translateSaved: "In 10 Sprachen gespeichert. Leser sehen die Übersetzung ihrer Sprache.",
251
+ translatePending: "Die Übersetzungen für 10 Sprachen sind bereit. Sie werden mit dem Beitrag gespeichert.",
252
+ translateEmpty: "Geben Sie zuerst einen Titel oder Text zum Übersetzen ein.",
249
253
  title: "Titel aus Text erstellen",
250
254
  custom: "In eigenem Stil umschreiben…",
251
255
  failed: "AI-Aufgabe fehlgeschlagen. Bitte erneut versuchen."
@@ -529,6 +533,11 @@ var e = {
529
533
  label: "Board-Sprache",
530
534
  auto: "Automatisch (Sprache der Website)"
531
535
  },
536
+ autoTranslate: {
537
+ help: "Beim Speichern werden Name und Beschreibung in 10 Sprachen übersetzt. Unveränderter Text wird nicht erneut übersetzt.",
538
+ failed: "Die Einstellungen wurden gespeichert, die Übersetzung in 10 Sprachen ist aber fehlgeschlagen. Speichern Sie erneut.",
539
+ ready: "Name und Beschreibung sind in 10 Sprachen gespeichert."
540
+ },
532
541
  action: {
533
542
  moveDown: "Nach unten",
534
543
  goToBoard: "Zum Board",
@@ -603,6 +612,9 @@ var e = {
603
612
  contentRequired: "Bitte zuerst Text für die AI eingeben.",
604
613
  customRequired: "Bitte eine Anweisung zum Umschreiben eingeben.",
605
614
  emptyResult: "Die AI konnte kein Ergebnis erzeugen. Bitte erneut versuchen.",
615
+ translateEmpty: "Geben Sie zuerst einen Titel oder Text zum Übersetzen ein.",
616
+ translateTooLong: "Texte über {limit} Zeichen können nicht in 10 Sprachen gespeichert werden. Kürzen Sie den Text und versuchen Sie es erneut.",
617
+ translateIncomplete: "Einige Sprachen kamen leer zurück. Bitte erneut versuchen.",
606
618
  failed: "AI-Aufgabe fehlgeschlagen. Bitte erneut versuchen.",
607
619
  actions: {
608
620
  proofread: "Rechtschreibung prüfen",
@@ -611,6 +623,10 @@ var e = {
611
623
  summarize: "Kurz zusammenfassen",
612
624
  translateEn: "Ins Englische übersetzen",
613
625
  translateKo: "Ins Koreanische übersetzen",
626
+ translateAll: "In 10 Sprachen übersetzen und speichern",
627
+ translateSaved: "In 10 Sprachen gespeichert. Leser sehen die Übersetzung ihrer Sprache.",
628
+ translatePending: "Die Übersetzungen für 10 Sprachen sind bereit. Sie werden mit dem Beitrag gespeichert.",
629
+ translateEmpty: "Geben Sie zuerst einen Titel oder Text zum Übersetzen ein.",
614
630
  title: "Titel aus Text erstellen",
615
631
  custom: "In eigenem Stil umschreiben…"
616
632
  }
@@ -619,4 +635,4 @@ var e = {
619
635
  //#endregion
620
636
  export { e as default };
621
637
 
622
- //# sourceMappingURL=de-ddWQNaqs.js.map
638
+ //# sourceMappingURL=de-B6gqgrqZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"de-B6gqgrqZ.js","names":[],"sources":["../src/i18n/messages/de.ts"],"sourcesContent":["import type { MemiBoardMessages } from '../translate'\n\n/** 짧은 화면 문구만 번역한다. 빠진 키는 en → ko 순으로 폴백된다. */\nconst messages: MemiBoardMessages = {\n attachments: {\n attach: 'Dateien anhängen ({count} / {max})',\n maxFiles: 'Maximal {max} Dateien möglich.',\n },\n auth: {\n writeRestricted: 'Wegen wiederholter Inhaltswarnungen sind Beiträge und Kommentare vorübergehend gesperrt.',\n strikeSuffix: ' (Warnung {count}/{threshold})',\n strikeRestricted: ' Nach {count} Warnungen sind Beiträge und Kommentare gesperrt.',\n notInitialized: '[memi-board] Firebase Auth ist nicht initialisiert.',\n },\n authorMemo: {\n tooLong: 'Notizen dürfen höchstens {max} Zeichen lang sein.',\n },\n authorMenu: {\n viewProfile: 'Profil ansehen',\n viewPosts: 'Beiträge ansehen',\n viewComments: 'Kommentare ansehen',\n memoAriaLabel: 'Notiz zu diesem Autor',\n memoTitle: 'Private Notiz',\n memoPlaceholder: 'Notiz zu diesem Autor…',\n bad: 'Schlecht',\n good: 'Gut',\n },\n avatarCropModal: {\n title: 'Profilbild zuschneiden',\n description: 'Wird quadratisch (1:1) angezeigt. Bereich bitte anpassen.',\n confirm: 'Zuschneiden & hochladen',\n },\n boardSettings: {\n signInRequired: 'Anmeldung erforderlich.',\n idAndNameRequired: 'Bitte Board-ID und Namen eingeben.',\n deleteIdRequired: 'Zum Löschen ist eine Board-ID erforderlich.',\n nameRequired: 'Bitte einen Board-Namen eingeben.',\n },\n boardUsers: {\n unsupportedRole: 'Nicht unterstützte Rolle.',\n roleDescription: {\n admin: 'Verwaltet Benutzerrollen, Board-Einstellungen und alle Inhalte.',\n user: 'Kann Beiträge und Kommentare schreiben und nur eigene Inhalte bearbeiten oder löschen.',\n },\n },\n commentFeed: {\n empty: 'Noch keine Kommentare.',\n emptyLiked: 'Noch keine Kommentare mit Likes.',\n loadFailed: 'Liste konnte nicht geladen werden: {error}',\n blinded: 'Dieser Kommentar wurde von einem Admin ausgeblendet.',\n },\n commentForm: {\n hiddenBoardDenied: 'In ausgeblendeten Boards kann nur das zuständige Team kommentieren.',\n denied: 'Keine Berechtigung, in diesem Board zu kommentieren.',\n signInRequired: 'Anmeldung erforderlich.',\n writeRestricted: 'Wegen wiederholter Inhaltswarnungen sind Beiträge und Kommentare vorübergehend gesperrt.',\n moderationBlocked: 'Enthält Inhalte, die nicht veröffentlicht werden können.',\n replyTo: 'Antwort an {name}',\n placeholder: 'Kommentar schreiben',\n submitReply: 'Antworten',\n submit: 'Kommentieren',\n signInToComment: 'Zum Kommentieren bitte anmelden.',\n },\n commentItem: {\n editRestricted: 'Das Bearbeiten von Kommentaren ist vorübergehend gesperrt.',\n moderationBlocked: 'Enthält Inhalte, die nicht veröffentlicht werden können.',\n editFailed: 'Kommentar konnte nicht bearbeitet werden.',\n blindFailed: 'Ausblendestatus konnte nicht geändert werden.',\n likeFailed: 'Like konnte nicht verarbeitet werden.',\n blinded: 'Dieser Kommentar wurde von einem Admin ausgeblendet.',\n edit: 'Kommentar bearbeiten',\n blind: 'Kommentar ausblenden',\n unblind: 'Kommentar einblenden',\n delete: 'Kommentar löschen',\n },\n commentList: {\n loading: 'Kommentare werden geladen',\n empty: 'Noch keine Kommentare.',\n loadMore: 'Weitere Kommentare',\n loadingMore: 'Weitere Kommentare werden geladen',\n },\n commentThread: {\n deleteConfirm: 'Diesen Kommentar löschen?',\n deleteFailed: 'Kommentar konnte nicht gelöscht werden.',\n hideReplies: 'Antworten ausblenden',\n showReplies: '{count} Antworten',\n loadMoreReplies: 'Weitere Antworten',\n },\n comments: {\n bodyRequired: 'Bitte einen Kommentar eingeben.',\n bodyTooLong: 'Kommentare dürfen höchstens {max} Zeichen lang sein.',\n replyTargetNotFound: 'Der Kommentar, auf den geantwortet wird, wurde nicht gefunden.',\n threadNotFound: 'Kommentar-Thread nicht gefunden.',\n hasRepliesCannotDelete: 'Kommentare mit Antworten können derzeit nicht gelöscht werden.',\n },\n common: {\n action: {\n cancel: 'Abbrechen',\n save: 'Speichern',\n saved: 'Gespeichert',\n edit: 'Bearbeiten',\n delete: 'Löschen',\n confirm: 'OK',\n close: 'Schließen',\n add: 'Hinzufügen',\n more: 'Mehr',\n prev: 'Zurück',\n next: 'Weiter',\n retry: 'Erneut versuchen',\n reply: 'Antworten',\n like: 'Gefällt mir',\n unlike: 'Gefällt mir nicht mehr',\n share: 'Teilen',\n report: 'Melden',\n signIn: 'Anmelden',\n signOut: 'Abmelden',\n write: 'Schreiben',\n toTop: 'Nach oben',\n },\n label: {\n anonymous: 'Anonym',\n user: 'Benutzer',\n image: 'Bild',\n draft: 'Entwurf',\n loading: 'Wird geladen…',\n },\n role: {\n admin: 'Admin',\n staff: 'Team',\n user: 'Benutzer',\n },\n date: {\n justNow: 'Gerade eben',\n fewSecondsAgo: 'Vor wenigen Sekunden',\n checking: 'Zeit wird geprüft',\n createdAt: 'Erstellt: {time}',\n updatedAt: 'Bearbeitet: {time}',\n },\n },\n detail: {\n deleteConfirm: 'Diesen Beitrag löschen? Das kann nicht rückgängig gemacht werden.',\n notFound: 'Beitrag nicht gefunden.',\n draftPreview: 'Unveröffentlichte Vorschau',\n draftNotice: 'Noch nicht veröffentlicht. Bis zur Veröffentlichung nur für dich sichtbar.',\n publish: 'Veröffentlichen',\n backToList: 'Zurück zur Liste',\n postNav: 'Beitragsnavigation',\n list: 'Liste',\n comments: 'Kommentare',\n },\n editor: {\n titlePlaceholder: 'Titel',\n contentPlaceholder: 'Text eingeben… / öffnet das Blockmenü.',\n imageContentPlaceholder: 'Text eingeben…',\n tagsPlaceholder: 'Tag eingeben, dann Enter',\n skipModeration: 'Schimpfwortfilter umgehen (Admin)',\n submitEdit: 'Änderungen speichern',\n submitNext: 'Weiter: Vorschau',\n apply: 'Übernehmen',\n imageOptimizing: 'Bild wird optimiert und hochgeladen…',\n cover: {\n imageLink: 'Bildlink',\n addPhoto: 'Fotos hinzufügen',\n alt: 'Foto {n}',\n representative: 'Titelbild',\n setRepresentative: 'Als Titelbild',\n setRepresentativeAria: 'Foto {n} als Titelbild festlegen',\n remove: 'Foto entfernen',\n add: 'Hinzufügen',\n uploading: 'Wird hochgeladen…',\n dropOrClick: 'Fotos ablegen oder klicken',\n emptyHint: 'Mehrfachauswahl · Titelbild nach Upload wählen · Ziehen zum Sortieren',\n uploadTitle: 'Hochladen',\n },\n uploadFailed: 'Upload fehlgeschlagen',\n aiAssistant: 'AI-Schreibassistent',\n insertBlock: 'Block einfügen',\n blockMenu: 'Blockmenü',\n imageDialog: {\n title: 'Bild hinzufügen',\n chooseFile: 'Bild vom Gerät auswählen',\n orLink: 'oder Bildlink',\n urlLabel: 'Bild-URL',\n previewAlt: 'Vorschau des externen Bildes',\n addLink: 'Link hinzufügen',\n },\n customDialog: {\n title: 'In eigenem Stil umschreiben',\n label: 'Wie soll umgeschrieben werden?',\n help: 'z. B. locker / förmlich und schriftlich / als knappe Stichpunkte',\n placeholder: 'Locker formuliert',\n },\n aiPreview: {\n title: 'AI-Ergebnis prüfen',\n currentTitle: 'Aktueller Titel',\n newTitle: 'Neuer Titel',\n currentContent: 'Aktueller Inhalt',\n appendedContent: 'Hinzuzufügender Inhalt',\n before: 'Vorher',\n after: 'Nachher',\n },\n menu: {\n ai: 'AI',\n aiContinue: 'Mit AI weiterschreiben',\n style: 'Stil',\n insert: 'Einfügen',\n paragraph: 'Absatz',\n heading1: 'Überschrift 1',\n heading2: 'Überschrift 2',\n heading3: 'Überschrift 3',\n bulletList: 'Aufzählung',\n orderedList: 'Nummerierte Liste',\n blockquote: 'Zitat',\n codeBlock: 'Codeblock',\n horizontalRule: 'Trennlinie',\n image: 'Bild',\n turnInto: 'Umwandeln in',\n resetFormatting: 'Formatierung zurücksetzen',\n duplicate: 'Duplizieren',\n moveUp: 'Nach oben',\n moveDown: 'Nach unten',\n delete: 'Löschen',\n },\n toolbar: {\n link: 'Link',\n image: 'Bild',\n format: 'Format',\n bold: 'Fett',\n italic: 'Kursiv',\n underline: 'Unterstrichen',\n strike: 'Durchgestrichen',\n title: 'Titel',\n heading: 'Überschrift',\n subheading: 'Zwischenüberschrift',\n body: 'Fließtext',\n mono: 'Monospace',\n bulletList: 'Aufzählung',\n orderedList: 'Nummerierte Liste',\n blockquote: 'Blockzitat',\n codeBlock: 'Codeblock',\n horizontalRule: 'Trennlinie',\n clearFormatting: 'Formatierung entfernen',\n undo: 'Rückgängig',\n redo: 'Wiederholen',\n },\n ai: {\n proofread: 'Rechtschreibung prüfen',\n polish: 'Text verbessern',\n continue: 'Weiterschreiben',\n summarize: 'Kurz zusammenfassen',\n translateEn: 'Ins Englische übersetzen',\n translateKo: 'Ins Koreanische übersetzen',\n translateAll: 'In 10 Sprachen übersetzen und speichern',\n translateSaved: 'In 10 Sprachen gespeichert. Leser sehen die Übersetzung ihrer Sprache.',\n translatePending: 'Die Übersetzungen für 10 Sprachen sind bereit. Sie werden mit dem Beitrag gespeichert.',\n translateEmpty: 'Geben Sie zuerst einen Titel oder Text zum Übersetzen ein.',\n title: 'Titel aus Text erstellen',\n custom: 'In eigenem Stil umschreiben…',\n failed: 'AI-Aufgabe fehlgeschlagen. Bitte erneut versuchen.',\n },\n youtubePrompt: 'YouTube-Link eingeben.',\n errors: {\n coverMax: 'Maximal {max} Fotos möglich.',\n imageUrlRequired: 'Bitte eine Bild-URL eingeben.',\n imageUrlInvalid: 'Bitte eine gültige Bild-URL eingeben.',\n imageUrlHttps: 'Nur HTTPS-Bild-URLs sind erlaubt.',\n imageUrlCredentials: 'URLs mit Anmeldedaten sind nicht erlaubt.',\n imageTimeout: 'Das Bild lädt zu lange.',\n imageNotDisplayable: 'Dieses Bild kann nicht angezeigt werden.',\n editorNotReady: 'Editor ist nicht bereit. Bitte erneut versuchen.',\n imageLinkFailed: 'Bildlink konnte nicht hinzugefügt werden.',\n imageOnly: 'Nur Bilddateien können hochgeladen werden.',\n imageTooLarge: 'Originalbilder dürfen höchstens {mb} MB groß sein.',\n imageUploadFailed: 'Bild-Upload fehlgeschlagen.',\n pasteAsFile: 'Fotos bitte als Dateien in den Bereich oben einfügen oder ziehen.',\n postNotFound: 'Beitrag nicht gefunden.',\n loadPermission: 'Keine Berechtigung, diesen Beitrag zu laden. Bitte Anmeldung prüfen.',\n loadFailed: 'Beitrag konnte nicht geladen werden: {message}',\n titleRequired: 'Bitte einen Titel eingeben.',\n photoRequired: 'Bitte mindestens ein Foto hochladen.',\n bodyRequired: 'Bitte Text eingeben oder ein Bild anhängen.',\n signInRequired: 'Anmeldung erforderlich.',\n boardMissing: 'Kein Board angegeben.',\n writeRestricted: 'Wegen wiederholter Inhaltswarnungen sind Beiträge und Kommentare vorübergehend gesperrt.',\n moderationBlocked: 'Enthält Inhalte, die nicht veröffentlicht werden können.',\n },\n hint: {\n reviewing: 'Inhalt wird geprüft…',\n saving: 'Wird gespeichert…',\n },\n },\n footer: {\n info: {\n title: 'Board-Info',\n about: 'Über dieses Board',\n history: 'Versionsverlauf',\n },\n stack: {\n ui: 'Oberfläche & Erlebnis',\n data: 'Daten & Anmeldung',\n writing: 'Schreiben & Komfort',\n },\n },\n likes: {\n signInRequired: 'Anmeldung erforderlich.',\n commentNotFound: 'Kommentar nicht gefunden.',\n },\n list: {\n settings: 'Einstellungen',\n loadFailed: 'Liste konnte nicht geladen werden: {error}',\n empty: 'Noch keine Beiträge.',\n photo: 'Foto',\n likeCount: 'Likes {count}',\n info: {\n title: 'Board-Info',\n about: 'Über dieses Board',\n history: 'Versionsverlauf',\n },\n stack: {\n framework: 'Framework & Sprache',\n ui: 'UI & Styling',\n backend: 'Backend & Daten',\n },\n tech: {\n nuxt: 'Vue-basiertes SSR-Framework',\n vue: 'Composition API · script setup',\n typescript: 'Typsichere Daten und Komponenten',\n nuxtUi: 'Board-, Formular- und Modal-UI',\n tailwind: 'Responsiv · helle/dunkle Stile',\n lucide: 'Leichte, einheitliche Icons',\n dayjs: 'Lesbare Datums- und relative Zeitangaben',\n firestore: 'Echtzeit-DB für Beiträge und Kommentare',\n auth: 'Sichere Anmeldung und Berechtigungen',\n storage: 'Speicher für Bilder und Anhänge',\n vuefire: 'Echtzeit-Anbindung von Nuxt und Firebase',\n },\n },\n listViewSwitch: {\n default: 'Standard',\n dense: 'Kompakt',\n video: 'Video',\n },\n miniComments: {\n empty: 'Noch keine Kommentare.',\n blinded: 'Dieser Kommentar wurde von einem Admin ausgeblendet.',\n },\n miniList: {\n empty: 'Noch keine Beiträge.',\n },\n miniPicker: {\n selectedCount: 'Ausgewählt {count} / {max}',\n layoutHint: 'Ab 2 als Karussell, 4 als Raster',\n searchPlaceholder: 'Titel suchen',\n loading: 'Beiträge werden geladen…',\n noMore: 'Keine weiteren Beiträge zur Auswahl.',\n },\n miniPost: {\n empty: 'Keine Beiträge ausgewählt.',\n },\n moderation: {\n writeRestricted: 'Wegen wiederholter Inhaltswarnungen sind Beiträge und Kommentare vorübergehend gesperrt.',\n },\n moderationPrompt: {\n localHit: 'Ausdrücke wie „{hit}“ sind nicht erlaubt. Bitte anders formulieren.',\n localGeneric: 'Enthält Schimpfwörter und kann nicht veröffentlicht werden. Bitte anders formulieren.',\n genericBlocked: 'Enthält Inhalte, die nicht veröffentlicht werden können. Bitte überarbeiten.',\n categories: {\n abuse: 'Schimpfwörter oder Beleidigungen',\n spam: 'Spam oder Werbung',\n adult: 'sexuelle oder nicht jugendfreie Inhalte',\n violence: 'Gewalt oder Drohungen',\n other: 'unangemessene Inhalte',\n },\n },\n moderationStrike: {\n liftSoon: 'endet bald',\n liftInHours: 'endet in ca. {hours} Std.',\n liftInDays: 'endet in ca. {days} T.',\n liftInDaysHours: 'endet in ca. {days} T. {hours} Std.',\n },\n posts: {\n boardIdRequired: '[memi-board] boardId ist erforderlich.',\n photoRequired: 'Bitte ein Foto hinzufügen.',\n bodyRequired: 'Bitte Text eingeben oder ein Bild anhängen.',\n titleRequired: 'Bitte einen Titel eingeben.',\n },\n profile: {\n notFound: 'Benutzer nicht gefunden.',\n noName: 'Kein Name',\n joined: 'Dabei seit {time}',\n lastVisit: 'Zuletzt aktiv {time}',\n },\n profileEditModal: {\n imageOnly: 'Nur Bilddateien können ausgewählt werden.',\n nameRequired: 'Bitte einen Namen eingeben.',\n saveFailed: 'Profil konnte nicht gespeichert werden.',\n title: 'Profil bearbeiten',\n changePhoto: 'Foto ändern',\n name: 'Name',\n bio: 'Über mich',\n bioPlaceholder: 'Erzähl etwas über dich',\n },\n reportButton: {\n failed: 'Meldung konnte nicht gesendet werden.',\n reported: 'Gemeldet',\n title: 'Beitrag melden',\n done: 'Meldung erhalten. Das Team prüft sie.',\n reason: 'Grund',\n detailPlaceholder: 'Details (optional)',\n submit: 'Melden',\n },\n reportList: {\n staffOnly: 'Nur das Team kann Meldungen einsehen.',\n loadFailed: 'Liste konnte nicht geladen werden: {error}',\n empty: 'Keine offenen Meldungen.',\n untitled: 'Beitrag ohne Titel',\n author: 'von {name}',\n dismiss: 'Ablehnen',\n confirm: 'Erledigt',\n },\n reports: {\n reason: {\n spam: 'Spam / Werbung',\n abuse: 'Beleidigung / Hass / Belästigung',\n adult: 'Sexuelle Inhalte',\n illegal: 'Illegale Inhalte',\n other: 'Sonstiges',\n },\n fallbackLabel: 'Meldung',\n signInRequired: 'Anmeldung erforderlich.',\n cannotReportOwn: 'Eigene Beiträge können nicht gemeldet werden.',\n reasonRequired: 'Bitte einen Grund auswählen.',\n detailTooLong: 'Details dürfen höchstens {max} Zeichen lang sein.',\n },\n sectionUtil: {\n kind: {\n list: 'Beitragsliste',\n post: 'Beiträge',\n comments: 'Kommentare',\n },\n sort: {\n latest: 'Neueste',\n likes: 'Beliebteste',\n },\n untitledPost: 'Beitrag',\n view: {\n media: 'Medien',\n list: 'Liste',\n },\n },\n sections: {\n defaultTitle: 'Abschnitt {n}',\n },\n seo: {\n allCategory: 'Alle',\n boardDescription: 'Board {name}',\n allDescription: 'Board – Ankündigungen und News aus der Community.',\n postFallbackTitle: 'Beitrag | {site}',\n postFallbackDescription: 'Board',\n photo: 'Foto',\n postDescriptionFallback: 'Board-Beitrag',\n listTitle: '{name} | {site}',\n listTitleAll: 'Board | {site}',\n listDescription: '{name} – die neuesten Beiträge.',\n listDescriptionAll: 'Board – Ankündigungen und Geschichten.',\n },\n settings: {\n loading: 'Einstellungen werden geladen…',\n noAccess: 'Kein Zugriff',\n notFound: {\n title: 'Board nicht gefunden',\n checkAddress: 'Bitte die Adresse prüfen.',\n checkBoardAddress: 'Bitte die Board-Adresse prüfen.',\n },\n newBoard: {\n title: 'Dieses Board gibt es noch nicht',\n description: 'Unten Angaben ausfüllen und speichern, um es mit dieser ID anzulegen.',\n },\n noLabel: 'Kein Label',\n option: {\n default: 'Standard',\n },\n id: {\n label: 'Kategorie-ID',\n help: 'Wird in URL und Daten verwendet',\n },\n writeRole: {\n label: 'Wer darf posten',\n help: 'Mindestrolle zum Posten in dieser Kategorie',\n helpShort: 'Mindestrolle zum Posten',\n options: {\n user: 'Ab Benutzer',\n staff: 'Ab Team',\n admin: 'Nur Admins',\n },\n },\n commentWriteRole: {\n label: 'Wer darf kommentieren',\n help: 'Mindestrolle zum Kommentieren in dieser Kategorie',\n helpShort: 'Mindestrolle zum Kommentieren',\n hiddenFixed: 'Nur zuständiges Team (fest bei ausgeblendeten Boards)',\n },\n staff: {\n label: 'Zuständiges Team',\n help: 'Nur die ausgewählten Teammitglieder können dieses Board einrichten und verwalten. Leer lassen = nur Admins',\n placeholder: 'Nur Admins (kein Team zugewiesen)',\n },\n label: {\n label: 'Anzeigename',\n help: 'Der für Benutzer sichtbare Name',\n },\n description: {\n label: 'Beschreibung',\n help: 'Wird u. a. oben im Board angezeigt',\n placeholder: 'Kurze Beschreibung dieses Boards (optional)',\n },\n visibility: {\n label: 'Sichtbarkeit',\n options: {\n public: {\n label: 'Sichtbar',\n description: 'In Gesamtliste und Filtern sichtbar',\n },\n hidden: {\n label: 'Ausgeblendet',\n description: 'Tagebuch oder privat. Nur das zuständige Team kann kommentieren',\n },\n },\n },\n listView: {\n label: 'Listenansicht',\n help: 'Darstellung der Beitragsliste',\n options: {\n dense: 'Kompakt',\n video: 'Video',\n },\n },\n editorType: {\n label: 'Beitragsformular',\n help: 'Art der Beitragserstellung',\n options: {\n default: 'Titel und Text schreiben',\n image: 'Mehrere Fotos und ein Titelbild hinzufügen',\n },\n },\n locale: {\n label: 'Board-Sprache',\n auto: 'Automatisch (Sprache der Website)',\n },\n autoTranslate: {\n help: 'Beim Speichern werden Name und Beschreibung in 10 Sprachen übersetzt. Unveränderter Text wird nicht erneut übersetzt.',\n failed: 'Die Einstellungen wurden gespeichert, die Übersetzung in 10 Sprachen ist aber fehlgeschlagen. Speichern Sie erneut.',\n ready: 'Name und Beschreibung sind in 10 Sprachen gespeichert.',\n },\n action: {\n moveDown: 'Nach unten',\n goToBoard: 'Zum Board',\n deleteBoard: 'Board löschen',\n create: 'Erstellen',\n },\n newCategoryPlaceholder: 'Name der neuen Kategorie',\n savedCategory: 'Kategorie „{label}“ gespeichert.',\n savedSettings: 'Board-Einstellungen gespeichert.',\n created: 'Board erstellt.',\n error: {\n orderFailed: 'Kategoriereihenfolge konnte nicht gespeichert werden.',\n deleteFailed: 'Board und zugehörige Daten konnten nicht gelöscht werden.',\n saveFailed: 'Board-Einstellungen konnten nicht gespeichert werden.',\n deleteAllFailed: 'Board-Daten konnten nicht gelöscht werden.',\n },\n danger: {\n title: 'Gefahrenbereich',\n confirmLabel: 'Zur Bestätigung „{phrase}“ eingeben',\n phrase: 'Board-Daten löschen',\n button: 'Alle Daten löschen',\n done: 'Alle Board-Daten wurden gelöscht.',\n },\n },\n shareButton: {\n copied: 'Link kopiert',\n },\n signIn: {\n withGoogle: 'Mit Google anmelden',\n withApple: 'Mit Apple anmelden',\n or: 'oder',\n name: 'Name',\n email: 'E-Mail',\n password: 'Passwort',\n signUp: 'Registrieren',\n toSignUp: 'Noch kein Konto? Registrieren',\n toSignIn: 'Bereits ein Konto? Anmelden',\n },\n storage: {\n sourceTooLarge: 'Das Originalbild darf höchstens {max} MB groß sein.',\n imageOnly: 'Nur Bilddateien können hochgeladen werden.',\n canvasUnavailable: 'Canvas ist nicht verfügbar',\n compressFailed: 'Komprimierung fehlgeschlagen',\n loadFailed: 'Bild konnte nicht geladen werden',\n },\n swipeHint: {\n title: 'Nach links oder rechts wischen',\n description: 'Nach links wischen für den nächsten, nach rechts für den vorherigen Beitrag.',\n },\n userCommentList: {\n loadFailed: 'Liste konnte nicht geladen werden: {error}',\n empty: 'Noch keine Kommentare.',\n blinded: 'Dieser Kommentar wurde von einem Admin ausgeblendet.',\n likes: '{count} Likes',\n },\n userPostList: {\n loadFailed: 'Liste konnte nicht geladen werden: {error}',\n empty: 'Noch keine Beiträge.',\n },\n users: {\n changeRoleFailed: 'Rolle konnte nicht geändert werden.',\n loading: 'Benutzer werden geladen…',\n noPermission: {\n title: 'Keine Berechtigung',\n description: 'Nur Board-Admins können Benutzerrollen verwalten.',\n },\n empty: 'Noch keine Benutzer im Board angemeldet.',\n noName: 'Kein Name',\n moderationWarnings: '{count} Inhaltswarnungen',\n },\n writingAssistant: {\n loginRequired: 'Zum Nutzen des AI-Schreibassistenten bitte anmelden.',\n userMissing: 'Benutzerdaten konnten nicht geprüft werden. Bitte erneut versuchen.',\n contentRequired: 'Bitte zuerst Text für die AI eingeben.',\n customRequired: 'Bitte eine Anweisung zum Umschreiben eingeben.',\n emptyResult: 'Die AI konnte kein Ergebnis erzeugen. Bitte erneut versuchen.',\n translateEmpty: 'Geben Sie zuerst einen Titel oder Text zum Übersetzen ein.',\n translateTooLong: 'Texte über {limit} Zeichen können nicht in 10 Sprachen gespeichert werden. Kürzen Sie den Text und versuchen Sie es erneut.',\n translateIncomplete: 'Einige Sprachen kamen leer zurück. Bitte erneut versuchen.',\n failed: 'AI-Aufgabe fehlgeschlagen. Bitte erneut versuchen.',\n actions: {\n proofread: 'Rechtschreibung prüfen',\n polish: 'Text verbessern',\n continue: 'Weiterschreiben',\n summarize: 'Kurz zusammenfassen',\n translateEn: 'Ins Englische übersetzen',\n translateKo: 'Ins Koreanische übersetzen',\n translateAll: 'In 10 Sprachen übersetzen und speichern',\n translateSaved: 'In 10 Sprachen gespeichert. Leser sehen die Übersetzung ihrer Sprache.',\n translatePending: 'Die Übersetzungen für 10 Sprachen sind bereit. Sie werden mit dem Beitrag gespeichert.',\n translateEmpty: 'Geben Sie zuerst einen Titel oder Text zum Übersetzen ein.',\n title: 'Titel aus Text erstellen',\n custom: 'In eigenem Stil umschreiben…',\n },\n },\n}\n\nexport default messages\n"],"mappings":"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"}
@@ -39,8 +39,10 @@ var e = {
39
39
  boardUsers: {
40
40
  unsupportedRole: "Unsupported role.",
41
41
  roleDescription: {
42
+ owner: "Manages administrators and the entire service.",
43
+ manager: "Manages settings and staff assignments for assigned boards.",
42
44
  admin: "Manages user roles, board settings, and all content.",
43
- staff: "Manages all posts and comments, but can’t change user roles or board settings.",
45
+ staff: "Manages posts and comments in assigned boards, but cannot change settings or assignments.",
44
46
  user: "Can write posts and comments, and edit or delete only their own content."
45
47
  }
46
48
  },
@@ -128,6 +130,8 @@ var e = {
128
130
  loading: "Loading…"
129
131
  },
130
132
  role: {
133
+ owner: "Owner",
134
+ manager: "Manager",
131
135
  admin: "Admin",
132
136
  staff: "Staff",
133
137
  user: "User"
@@ -255,6 +259,10 @@ var e = {
255
259
  summarize: "Summarize briefly",
256
260
  translateEn: "Translate to English",
257
261
  translateKo: "Translate to Korean",
262
+ translateAll: "Translate and save in 10 languages",
263
+ translateSaved: "Saved in 10 languages. Readers see the translation for their language.",
264
+ translatePending: "Translations for 10 languages are ready. They are stored when you save the post.",
265
+ translateEmpty: "Enter a title or body to translate first.",
258
266
  title: "Generate title from body",
259
267
  custom: "Rewrite in a custom style…",
260
268
  failed: "The AI task failed. Please try again."
@@ -466,6 +474,7 @@ var e = {
466
474
  listDescriptionAll: "Board — announcements and stories."
467
475
  },
468
476
  settings: {
477
+ managers: "Assigned managers",
469
478
  loading: "Loading settings…",
470
479
  noAccess: "You don’t have access",
471
480
  notFound: {
@@ -550,6 +559,11 @@ var e = {
550
559
  help: "Leave empty to follow the site language. If set, this board always appears in that language.",
551
560
  auto: "Auto (follow site language)"
552
561
  },
562
+ autoTranslate: {
563
+ help: "Saving translates the name and description into 10 languages. Unchanged text is not translated again.",
564
+ failed: "Settings were saved, but translating into 10 languages failed. Save again to retry.",
565
+ ready: "The name and description are saved in 10 languages."
566
+ },
553
567
  action: {
554
568
  moveDown: "Down",
555
569
  goToBoard: "Go to board",
@@ -630,6 +644,9 @@ var e = {
630
644
  contentRequired: "Enter some content for the AI to work on first.",
631
645
  customRequired: "Enter an instruction for how to rewrite it.",
632
646
  emptyResult: "The AI couldn’t produce a result. Please try again.",
647
+ translateEmpty: "Enter a title or body to translate first.",
648
+ translateTooLong: "Posts longer than {limit} characters can’t be saved in 10 languages. Shorten it and try again.",
649
+ translateIncomplete: "Some languages came back empty. Please try again.",
633
650
  failed: "The AI task failed. Please try again.",
634
651
  actions: {
635
652
  proofread: "Check spelling",
@@ -638,6 +655,7 @@ var e = {
638
655
  summarize: "Summarize",
639
656
  translateEn: "Translate to English",
640
657
  translateKo: "Translate to Korean",
658
+ translateAll: "Translate and save in 10 languages",
641
659
  title: "Generate title from body",
642
660
  custom: "Rewrite in a custom style…"
643
661
  }
@@ -646,4 +664,4 @@ var e = {
646
664
  //#endregion
647
665
  export { e as default };
648
666
 
649
- //# sourceMappingURL=en-DxkqyZq2.js.map
667
+ //# sourceMappingURL=en-BaP_ikG-.js.map