memi-board 0.52.0 → 0.52.2

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.
@@ -1,42 +1,42 @@
1
- # 국제화와 콘텐츠 번역 설정
1
+ # Internationalization and content translation
2
2
 
3
- [설치 가이드](host-integration.md) · [README](../README.md) · [Firebase 준비](firebase-setup.md)
3
+ [Installation guide](host-integration.md) · [README](../README.md) · [Firebase setup](firebase-setup.md)
4
4
 
5
- `i18n.enabled`·`translation.enabled`는 **memi-board 0.47.0 이상**에서 지원한다. 기존 호스트는 `pnpm add memi-board@^0.47.0`으로 업데이트하고 lockfile도 함께 반영한다.
5
+ `i18n.enabled` and `translation.enabled` are supported in **memi-board 0.47.0 or later**. Existing host apps should update with `pnpm add memi-board@^0.47.0` and commit the lockfile as well.
6
6
 
7
- ## 1. 서비스에 맞는 모드 고르기
7
+ ## 1. Choose a mode for your service
8
8
 
9
- **UI 언어**는 저장·취소·안내문·날짜 같은 화면 문구의 언어다. **콘텐츠 번역**은 게시판 이름·설명, 섹션 제목, 글 제목·요약·본문을 다른 언어로 생성하고 표시하는 기능이다.
9
+ **UI language** is the language of on-screen text such as save, cancel, help messages, and dates. **Content translation** generates and displays board names and descriptions, section titles, and post titles, summaries, and bodies in other languages.
10
10
 
11
- | 사용 목적 | `i18n.enabled` | `translation.enabled` | 동작 |
11
+ | Purpose | `i18n.enabled` | `translation.enabled` | Behavior |
12
12
  |---|---|---|---|
13
- | 한국어 또는 영어만 사용하는 사이트 | `false` | 지정 불필요 | `locale`로 UI 고정, 콘텐츠 원문 표시, 번역 기능 숨김 |
14
- | 다국어 UI만 필요한 사이트 | `true` | `false` | UI 언어 전환, 콘텐츠 원문 표시, 번역 기능 숨김 |
15
- | 다국어 UI와 콘텐츠 번역이 필요한 사이트 | `true` | `true` | UI 언어 전환과 콘텐츠 번역 모두 사용 |
16
- | 기존 설정 그대로 | 생략 | 생략 | 두 옵션 모두 기본 `true` |
13
+ | Site that uses only Korean or only English | `false` | Not needed | UI fixed by `locale`, content shown in the original, translation features hidden |
14
+ | Site that needs only a multilingual UI | `true` | `false` | UI language switching, content shown in the original, translation features hidden |
15
+ | Site that needs a multilingual UI and content translation | `true` | `true` | Both UI language switching and content translation |
16
+ | Keep existing behavior | Omitted | Omitted | Both options default to `true` |
17
17
 
18
- `i18n.enabled: false`가 우선한다. 이때 `translation.enabled: true`를 지정해도 콘텐츠 번역은 실행하지 않는다. 두 기능을 끄더라도 기존 번역 데이터는 삭제하지 않는다.
18
+ `i18n.enabled: false` takes precedence. In that case, content translation does not run even if `translation.enabled: true` is set. Turning off either feature does not delete existing translation data.
19
19
 
20
- ## 2. 복사해서 사용할 설정
20
+ ## 2. Ready-to-copy configurations
21
21
 
22
- 아래 `memiBoard` 블록을 호스트의 `defineNuxtConfig({ ... })` 안에 넣는다. Firebase 초기화 등 나머지 설정은 [설치 가이드](host-integration.md)를 따른다.
22
+ Put the `memiBoard` block below inside the host's `defineNuxtConfig({ ... })`. For the rest, such as Firebase initialization, follow the [installation guide](host-integration.md).
23
23
 
24
- ### 한 언어로만 운영
24
+ ### Single language only
25
25
 
26
26
  ```ts
27
27
  memiBoard: {
28
- locale: 'ko', // 영어 전용이면 'en'
28
+ locale: 'ko', // 'en' for English-only
29
29
  i18n: { enabled: false }
30
30
  }
31
31
  ```
32
32
 
33
- - 호스트의 현재 언어와 게시판별 언어를 무시하고 `locale`를 사용한다. 생략하거나 지원하지 않는 값이면 `ko`다.
34
- - 게시판 언어 선택, 번역 버튼, 자동 번역 토글, 글쓰기 AI의 번역 메뉴를 숨긴다.
35
- - 저장된 번역이 있어도 콘텐츠는 원문으로 표시한다.
36
- - 게시판의 자동 번역 토글이 이전에 켜져 있어도 설정 저장 시 번역하지 않는다.
37
- - `@nuxtjs/i18n`을 설치할 필요가 없다. 호스트가 이미 쓰고 있어도 게시판은 지정 언어로 고정된다.
33
+ - Uses `locale`, ignoring the host's current language and per-board languages. If omitted or unsupported, it is `ko`.
34
+ - Hides the board language selector, translate buttons, the auto-translate toggle, and the translate menu in the writing AI.
35
+ - Content is shown in the original even if saved translations exist.
36
+ - Even if a board's auto-translate toggle was previously on, nothing is translated when the settings are saved.
37
+ - `@nuxtjs/i18n` does not need to be installed. Even if the host app already uses it, the board stays fixed to the specified language.
38
38
 
39
- ### UI는 다국어, 콘텐츠는 원문
39
+ ### Multilingual UI, original content
40
40
 
41
41
  ```ts
42
42
  memiBoard: {
@@ -46,9 +46,9 @@ memiBoard: {
46
46
  }
47
47
  ```
48
48
 
49
- 게시판 언어 선택은 유지하고 번역 관련 UI만 숨긴다. 메뉴·버튼·날짜는 현재 화면 언어를 따르고, 게시판 라벨·설명·섹션 제목·게시글은 작성한 원문을 보여 준다.
49
+ Keeps the board language selector and hides only the translation-related UI. Menus, buttons, and dates follow the current screen language, while board labels, descriptions, section titles, and posts are shown as originally written.
50
50
 
51
- ### UI와 콘텐츠 번역 모두 사용
51
+ ### Multilingual UI and content translation
52
52
 
53
53
  ```ts
54
54
  memiBoard: {
@@ -58,24 +58,24 @@ memiBoard: {
58
58
  }
59
59
  ```
60
60
 
61
- 번역을 생성하려면 호스트 Firebase 프로젝트에 AI Logic 연결을 준비해야 한다. UI 언어 파일을 보여 주는 것만으로는 AI 요청이 발생하지 않는다.
61
+ To generate translations, the host's Firebase project must have an AI Logic connection set up. Simply displaying UI language files does not trigger AI requests.
62
62
 
63
- ## 3. 호스트의 언어 전환 연결
63
+ ## 3. Connect the host app's language switching
64
64
 
65
- 지원 언어는 `ko`, `en`, `ja`, `de`, `fr`, `es`, `pt`, `zh`, `ar`, `id`다. `en-US`·`pt_BR` 같은 값은 지원 코드로 정규화한다. `i18n.enabled`가 켜져 있을 때 언어 결정 순서는 다음과 같다.
65
+ Supported languages are `ko`, `en`, `ja`, `de`, `fr`, `es`, `pt`, `zh`, `ar`, and `id`. Values such as `en-US` and `pt_BR` are normalized to supported codes. When `i18n.enabled` is on, the language is resolved in this order:
66
66
 
67
- 1. 게시판 설정의 고정 언어
68
- 2. 부모 게시판 화면이 전달한 언어
69
- 3. 호스트 `@nuxtjs/i18n`의 현재 언어
67
+ 1. The fixed language in the board settings
68
+ 2. The language passed from the parent board screen
69
+ 3. The current language of the host's `@nuxtjs/i18n`
70
70
  4. `memiBoard.locale`
71
71
  5. `ko`
72
72
 
73
- 사이트 언어 전환을 그대로 따르려면 게시판 설정의 언어를 **자동**으로 둔다. 게시판 언어를 따로 고정하면 사이트 언어보다 우선한다.
73
+ To follow the site's language switching, set the board's language to **Auto** in the board settings. A language fixed per board takes precedence over the site language.
74
74
 
75
- 호스트에 `@nuxtjs/i18n`을 사용하는 경우:
75
+ If the host app uses `@nuxtjs/i18n`:
76
76
 
77
77
  ```ts
78
- // nuxt.config.ts — 기존 설정에 병합
78
+ // nuxt.config.ts — merge into the existing config
79
79
  modules: ['@nuxt/ui', '@nuxtjs/i18n', 'nuxt-vuefire', 'memi-board'],
80
80
  i18n: {
81
81
  locales: [
@@ -92,9 +92,9 @@ memiBoard: {
92
92
  }
93
93
  ```
94
94
 
95
- 이 예제는 한국어·영어 UI만 사용한다. 호스트의 `i18n/locales/ko.json`·`en.json`에 호스트 UI용 메시지를 만든다(처음에는 `{}`도 가능). 게시판 자체의 번역 파일은 패키지가 제공하므로 복사하지 않는다. 나머지 언어도 필요할 때 호스트 목록에 추가한다.
95
+ This example uses only Korean and English UI. Create messages for the host UI in the host's `i18n/locales/ko.json` and `en.json` (`{}` is fine to start). The package ships the board's own translation files, so don't copy them. Add other languages to the host list as needed.
96
96
 
97
- Nuxt UI 내부 문구와 사이트 `<html>` 언어는 호스트가 연결한다. 예를 들어 한국어·영어 호스트는 `app/app.vue`에서 다음처럼 설정할 수 있다.
97
+ The host app wires up Nuxt UI's internal strings and the site's `<html>` language. For example, a Korean/English host can set this in `app/app.vue`:
98
98
 
99
99
  ```vue
100
100
  <script setup lang="ts">
@@ -111,52 +111,52 @@ useHead({ htmlAttrs: { lang: () => uiLocale.value.code, dir: () => uiLocale.valu
111
111
  </template>
112
112
  ```
113
113
 
114
- 레이아웃을 사용하는 사이트는 기존 `<NuxtLayout>` 구조를 유지한다. 게시판이 단일 언어이고 호스트는 다국어인 경우, 사이트 전체의 `<html>`·`UApp` 언어는 호스트 정책대로 결정한다.
114
+ Sites that use layouts keep their existing `<NuxtLayout>` structure. If the board is single-language and the host is multilingual, the site-wide `<html>` and `UApp` language are determined by the host's policy.
115
115
 
116
- ## 4. 번역이 실행되고 저장되는 시점
116
+ ## 4. When translations run and are saved
117
117
 
118
- | 대상 | 실행 시점 | 저장 시점 |
118
+ | Target | When it runs | When it is saved |
119
119
  |---|---|---|
120
- | 게시판 라벨·설명: 수동 | **10개 언어로 번역** 클릭 | 번역 후 설정의 **저장** 클릭 |
121
- | 게시판 라벨·설명: 자동 | 자동 번역 토글을 켜고 **저장** 클릭 | 번역 완료 후 설정과 함께 저장 |
122
- | 홈 섹션 제목 | 섹션 레이아웃 저장 | 번역 후 레이아웃과 함께 저장 |
123
- | 기존 글의 제목·요약·본문 | 글쓰기 AI의 **10개 언어로 번역해 저장** 클릭 | 번역 완료 후 해당 글에 즉시 저장 |
124
- | 새 글: 자동 | 보드 설정의 **글 작성 시 자동 번역**을 켠 뒤 새 글 저장 | 제목·요약·본문을 번역해 글과 함께 저장 |
125
- | 아직 저장하지 않은 새 글 | 글쓰기 AI의 **10개 언어로 번역해 저장** 클릭 | 새 글을 저장할 때 함께 저장 |
120
+ | Board label and description: manual | Click **Translate into 10 languages** | Click **Save** in the settings after translating |
121
+ | Board label and description: auto | Turn on the auto-translate toggle and click **Save** | Saved with the settings after translation completes |
122
+ | Home section titles | Saving the section layout | Saved with the layout after translation |
123
+ | Title, summary, and body of an existing post | Click **Translate and save in 10 languages** in the writing AI | Saved to the post immediately after translation completes |
124
+ | New post: auto | Turn on **Automatically translate new posts** in the board settings, then save a new post | Title, summary, and body are translated and saved with the post |
125
+ | New post not yet saved | Click **Translate and save in 10 languages** in the writing AI | Saved together when the new post is saved |
126
126
 
127
- 10개 언어에는 원문 언어가 포함된다. 원문이 한국어이면 나머지 9개 언어로 번역하고 한국어 원문을 합친다. 번역 버튼을 누르거나 저장할 때까지는 입력만으로 번역 요청을 보내지 않는다.
127
+ The 10 languages include the source language. If the original is Korean, it is translated into the other 9 languages and combined with the Korean original. Typing alone does not send translation requests until you click a translate button or save.
128
128
 
129
- 게시판 이름·설명과 섹션 제목이 번역 당시와 같고 번역이 모두 있으면 재번역을 건너뛴다. 게시판의 자동 번역 토글은 기본 꺼짐이다. `i18n.enabled`·`translation.enabled`의 기본 켜짐은 서비스에서 번역을 사용할 수 있다는 뜻이며, 게시판 자동 번역 토글을 자동으로 켜지는 않는다.
129
+ If the board name, description, and section titles are the same as when they were translated and all translations exist, retranslation is skipped. The board's auto-translate toggle is off by default. `i18n.enabled` and `translation.enabled` being on by default means the service can use translation; it does not automatically turn on a board's auto-translate toggle.
130
130
 
131
- `autoTranslatePosts`는 새 글 작성 전용이며 기본값은 `false`다. 기존 게시판에 필드가 없어도 꺼짐으로 처리한다. 글 수정 시에는 자동 번역하지 않는다. 수동으로 준비한 번역이 현재 내용과 일치하면 재사용한다. 자동 번역에 실패하면 입력을 유지하고 오류를 표시하며, 글 저장을 중단한다. 서비스 전체의 번역 기능이 꺼져 있으면 이 옵션과 무관하게 번역 없이 저장한다.
131
+ `autoTranslatePosts` applies only to writing new posts and defaults to `false`. Existing boards without the field are treated as off. Posts are not auto-translated on edit. Manually prepared translations are reused if they match the current content. If auto-translation fails, the input is kept, an error is shown, and saving the post is aborted. If translation is turned off service-wide, posts are saved without translation regardless of this option.
132
132
 
133
- 프로그램에서 `translateBoard()`를 직접 호출하면 현재 게시판 자동 번역 토글과 무관하게 번역할 수 있다. 서비스 전체의 국제화·번역 옵션이 꺼져 있으면 원문을 그대로 반환한다.
133
+ Calling `translateBoard()` directly from code translates regardless of the board's current auto-translate toggle. If the service-wide internationalization or translation option is off, it returns the original as-is.
134
134
 
135
- ## 5. AI 연결과 실패 처리
135
+ ## 5. AI connection and failure handling
136
136
 
137
- [Firebase 준비 문서](firebase-setup.md)의 AI Logic·App Check 절차를 호스트 프로젝트에 적용한다. Firebase 웹 앱의 `projectId`, `appId`, App Check 등록 대상이 일치해야 한다. 번역 모델과 App Check 토큰 옵션은 기존 `memiBoard.moderation.model`·`moderation.useLimitedUseAppCheckTokens`를 공유한다.
137
+ Apply the AI Logic and App Check steps from the [Firebase setup guide](firebase-setup.md) to the host project. The Firebase web app's `projectId`, `appId`, and App Check registration must match. The translation model and App Check token options share the existing `memiBoard.moderation.model` and `moderation.useLimitedUseAppCheckTokens`.
138
138
 
139
- `moderation.enabled: false`는 **검열**을 끄는 설정이다. 번역까지 끄려면 `translation.enabled: false` 또는 `i18n.enabled: false`를 사용한다. 반대로 번역을 꺼도 맞춤법 교정·문장 다듬기 같은 글쓰기 도우미와 검열은 각자의 설정을 따른다.
139
+ `moderation.enabled: false` turns off **moderation**. To also turn off translation, use `translation.enabled: false` or `i18n.enabled: false`. Conversely, turning off translation leaves writing assistants such as spell checking and sentence polishing, and moderation, governed by their own settings.
140
140
 
141
- | 상황 | 처리 |
141
+ | Situation | Handling |
142
142
  |---|---|
143
- | 게시판 수동 번역 실패 | 입력 내용을 유지하고 오류 표시. 설정 저장은 사용자가 결정 |
144
- | 게시판 자동 번역 실패 | 설정과 원문은 저장하고 번역 실패 안내 표시 |
145
- | 섹션 번역 실패 | 원문 레이아웃 저장, 콘솔 경고 |
146
- | 게시글 번역 실패 | 오류 표시. 번역 결과 저장 단계로 진행하지 않음 |
147
- | 서비스 옵션으로 번역을 끈 상태 | 자동 번역을 건너뛰고 정상 저장. AI 번역 직접 호출은 `memi-board/translation-disabled` 오류 |
143
+ | Manual board translation fails | Input is kept and an error is shown. The user decides whether to save the settings |
144
+ | Automatic board translation fails | Settings and the original are saved, and a translation failure notice is shown |
145
+ | Section translation fails | The original layout is saved, with a console warning |
146
+ | Post translation fails | An error is shown. It does not proceed to saving the translation result |
147
+ | Translation turned off by service option | Auto-translation is skipped and saving proceeds normally. Calling AI translation directly throws a `memi-board/translation-disabled` error |
148
148
 
149
- 게시글 AI 번역은 언어 수와 무관하게 글쓰기 도우미 일일 사용량 1회로 처리한다. 번역 비활성화에 따른 호출 거절은 AI 요청이나 사용량 갱신 전에 이루어진다. 활성 상태에서 이미 시작한 요청의 실패는 사용량이 차감될 수 있다.
149
+ AI post translation counts as 1 use of the daily writing assistant quota regardless of the number of languages. Calls rejected because translation is disabled are rejected before any AI request or usage update. When enabled, a request that has already started and then fails may still be counted against usage.
150
150
 
151
- ## 6. 기존 데이터와 다시 활성화하기
151
+ ## 6. Existing data and re-enabling
152
152
 
153
- 번역은 기존 문서의 `i18n`·`i18nSource`에 저장하며, 게시글 본문 번역은 해당 본문 문서에 저장한다. 기능을 끄기 위해 데이터 마이그레이션이나 번역 필드 삭제를 할 필요는 없다.
153
+ Translations are stored in the existing documents' `i18n` and `i18nSource`, and post body translations are stored in the corresponding body document. You don't need to migrate data or delete translation fields to turn the feature off.
154
154
 
155
- 원문이 번역 당시와 다르면 해당 번역 대신 원문을 표시한다. 다시 활성화했을 때도 이 검사를 유지한다. 예를 들어 번역을 끈 동안 게시판 이름을 바꿨다면, 다시 번역하기 전까지 이전 이름의 번역을 보여 주지 않는다.
155
+ If the original differs from what it was at translation time, the original is shown instead of that translation. This check stays in place after re-enabling. For example, if you renamed a board while translation was off, the translations of the old name are not shown until you translate again.
156
156
 
157
- ## 7. 호스트가 직접 만드는 UI
157
+ ## 7. UI built by the host app
158
158
 
159
- 패키지는 자체 UI만 숨긴다. 호스트가 만든 언어 선택기나 번역 버튼은 호스트에서 아래 상태를 사용해 제어한다.
159
+ The package hides only its own UI. Language selectors or translate buttons built by the host app should be controlled in the host using the state below.
160
160
 
161
161
  ```vue
162
162
  <script setup lang="ts">
@@ -165,19 +165,23 @@ const { i18nEnabled, translationEnabled } = useMemiBoardI18n()
165
165
  </script>
166
166
 
167
167
  <template>
168
- <span v-if="i18nEnabled">언어 전환 가능</span>
169
- <span v-if="translationEnabled">콘텐츠 번역 사용 가능</span>
168
+ <span v-if="i18nEnabled">Language switching available</span>
169
+ <span v-if="translationEnabled">Content translation available</span>
170
170
  </template>
171
171
  ```
172
172
 
173
- 컴포넌트 밖에서는 `isMemiBoardI18nEnabled()`·`isMemiBoardTranslationEnabled()`를 사용한다. 두 함수는 `memi-board/runtime`에서 명시적으로 가져온다. `configureMemiBoard()`도 같은 옵션을 받지만, Nuxt 호스트에서는 `nuxt.config.ts`의 `memiBoard`만 지정하면 된다.
173
+ Outside components, use `isMemiBoardI18nEnabled()` and `isMemiBoardTranslationEnabled()`. Import both explicitly from `memi-board/runtime`. `configureMemiBoard()` accepts the same options, but in a Nuxt host app you only need to set `memiBoard` in `nuxt.config.ts`.
174
174
 
175
- ## 8. 확인할 항목
175
+ ## 8. Checklist
176
176
 
177
- - 단일 언어: 호스트를 다른 언어로 바꿔도 게시판 버튼·날짜가 지정 언어를 유지하는지 확인한다.
178
- - 콘텐츠 번역만 끄기: UI는 바뀌고 기존 번역이 있는 글도 원문을 표시하는지 확인한다.
179
- - 자동 번역이 켜진 게시판을 저장해도 번역 비활성화 상태에서는 AI 요청이 없는지 확인한다.
180
- - 다시 활성화: 변경되지 않은 원문은 저장된 번역을 쓰고, 변경한 원문은 오래된 번역을 쓰지 않는지 확인한다.
181
- - 번역 실패 시 입력과 설정이 보존되는지 확인한다.
177
+ - Single language: verify that the board's buttons and dates keep the specified language even when the host switches to another language.
178
+ - Content translation off only: verify that the UI changes and that posts with existing translations still show the original.
179
+ - Verify that saving a board with auto-translate on makes no AI requests while translation is disabled.
180
+ - Re-enabling: verify that unchanged originals use the saved translations and changed originals don't use stale translations.
181
+ - Verify that input and settings are preserved when translation fails.
182
182
 
183
- 패키지 개발 환경의 회귀 검사는 `pnpm test:i18n`으로 실행한다.
183
+ Run the package's regression tests in the development environment with `pnpm test:i18n`.
184
+
185
+ ### 게시글 원문 언어 (0.52.1+)
186
+
187
+ 게시글 수동·자동 번역은 화면 언어 대신 제목·본문의 실제 언어를 판별해 원문 언어로 저장한다. 영어 화면에서 한국어 글을 번역해도 한국어 원문을 유지하고 영어 번역을 생성한다. 호스트에서 `translatePost({ source })`를 명시하면 그 값을 사용한다. 언어 판별은 번역과 같은 일일 사용 1회 안에서 실행된다. 이전 버전에서 화면 언어와 원문 언어가 다른 상태로 번역해 잘못 저장된 글은 재번역해야 한다.
@@ -1,36 +1,36 @@
1
- # 0.46 역할 마이그레이션
1
+ # 0.46 role migration
2
2
 
3
- [호스트 설치 가이드](host-integration.md) · [국제화·번역 설정](internationalization.md) · [Firebase 준비](firebase-setup.md)
3
+ [Host installation guide](host-integration.md) · [Internationalization and translation](internationalization.md) · [Firebase setup](firebase-setup.md)
4
4
 
5
- 신규 설치는 로그인으로 생성된 사용자 문서의 역할을 신뢰할 수 있는 콘솔 또는 서버에서 `owner`로 지정한다. 아래 admin → owner·staff → manager 이전은 기존 3개 역할을 쓰던 호스트에만 필요하다. 새로 만든 admin을 반복해서 owner로 바꾸는 작업이 아니다.
5
+ For a new install, set the role of the user document created on sign-in to `owner` from a trusted console or server. The admin → owner and staff → manager migration below is needed only for host apps that used the previous 3 roles. It is not a task of repeatedly converting newly created admins to owner.
6
6
 
7
- 역할: user(일반 사용자), staff(콘텐츠 담당), manager(담당 게시판 설정·스태프 배정), admin(전체 관리), owner(관리자 지정).
7
+ Roles: user (regular user), staff (content), manager (settings and staff assignment for assigned boards), admin (full management), owner (assigns administrators).
8
8
 
9
- `allowedStaffUids`는 콘텐츠 담당자, `allowedManagerUids`는 설정 담당자다. manager도 다른 게시판의 allowedStaffUids에만 배정되면 그 게시판에서는 콘텐츠만 관리한다. 글로벌 역할은 자격이고 실제 범위는 게시판 문서가 정한다. 매장 등 호스트의 조직을 게시판에 연결하고 배정을 동기화하는 책임은 호스트에 있다.
9
+ `allowedStaffUids` lists content staff, and `allowedManagerUids` lists settings managers. If a manager is assigned only to another board's allowedStaffUids, they manage only content on that board. The global role is an eligibility; the actual scope is determined by the board document. Linking the host's organizations, such as stores, to boards and syncing assignments is the host app's responsibility.
10
10
 
11
- 호환성을 위해 `isAdmin`은 owner를, `isStaff`는 manager를 포함한다. 정확한 역할은 `role`, `isOwner`, `isManager`로 확인한다. 설정 화면 진입에는 `canConfigureBoard(boardId)`, 콘텐츠 관리에는 `canManageBoard(boardId)`를 사용한다. 글·댓글 작성 최소 역할은 기존 user/staff/admin을 유지한다(manager는 staff 수준, owner는 admin 수준).
11
+ For compatibility, `isAdmin` includes owner and `isStaff` includes manager. Check the exact role with `role`, `isOwner`, and `isManager`. Use `canConfigureBoard(boardId)` for entering the settings screen and `canManageBoard(boardId)` for content management. The minimum roles for writing posts and comments remain user/staff/admin (manager counts as staff level, owner as admin level).
12
12
 
13
13
  ```ts
14
14
  // nuxt.config.ts
15
15
  memiBoard: {
16
- roleLabels: { user: '고객', staff: '매장 직원', manager: '매장 관리자', admin: '운영자', owner: '최고 관리자' }
16
+ roleLabels: { user: 'Customer', staff: 'Store staff', manager: 'Store manager', admin: 'Operator', owner: 'Super admin' }
17
17
  }
18
18
  ```
19
19
 
20
- 생략하면 언어별 기본 명칭을 쓴다. 커스텀 명칭은 모든 언어에 동일하게 적용된다.
20
+ If omitted, the default labels for each language are used. Custom labels apply identically to all languages.
21
21
 
22
- ## 적용 순서
22
+ ## Rollout order
23
23
 
24
- 1. 호스트에 새 패키지와 역할 인식 코드를 반영한다.
25
- 2. Firestore·Storage 규칙 예제를 호스트의 기존 규칙에 병합하고 에뮬레이터로 검사한다.
26
- 3. 새 규칙을 배포한다. App Hosting 배포만으로 규칙이 갱신되지 않는다.
27
- 4. 신뢰할 수 있는 Admin SDK로 기존 admin을 owner로, 기존 staff를 manager로 옮긴다. 각 게시판의 기존 staff UID를 allowedManagerUids로 이전한다. 일반 사용자는 그대로 둔다. 기존 admin이 여럿이면 모두 owner로 이전하므로 사전에 수량을 검토한다.
28
- 5. 새 호스트를 배포하고 로그인 상태를 새로고침한다.
24
+ 1. Apply the new package and role-aware code to the host app.
25
+ 2. Merge the Firestore and Storage rule examples into the host's existing rules and test them in the emulator.
26
+ 3. Deploy the new rules. An App Hosting deployment alone does not update the rules.
27
+ 4. With a trusted Admin SDK, move existing admins to owner and existing staff to manager. Move each board's existing staff UIDs to allowedManagerUids. Leave regular users as they are. If there are several existing admins, all of them become owners, so review the count beforehand.
28
+ 5. Deploy the new host app and refresh sign-in state.
29
29
 
30
- memi-dev의 scripts/migrate-board-roles.mjs는 기본 dry-run, --apply로 트랜잭션 적용한다. 이전 역할·배정은 클라이언트 접근이 막힌 memiBoardMigrations/roles-v046에 기록한다. 완료 마커가 있으면 재실행해도 신규 역할을 재승격하지 않는다. owner의 신규 지정·이관은 신뢰할 수 있는 서버 작업으로만 수행한다.
30
+ memi-dev's scripts/migrate-board-roles.mjs runs as a dry run by default and applies changes in a transaction with --apply. Previous roles and assignments are recorded in memiBoardMigrations/roles-v046, which is blocked from client access. If the completion marker exists, rerunning does not re-promote new roles. Assigning or transferring owner must be done only through trusted server operations.
31
31
 
32
- ## 파일 권한
32
+ ## File permissions
33
33
 
34
- Storage 규칙의 Firestore 조회 제한 때문에 기본 규칙은 작성자와 전역 admin/owner만 타인 파일을 수정·삭제할 수 있게 한다. staff/manager에게 전역 파일 권한을 주지 않는다. staff/manager의 타인 글 삭제 시 파일 정리는 기존 best-effort 방식이므로 남은 파일은 호스트 서버에서 정리해야 한다. 담당자의 타인 이미지·첨부 교체가 필요한 호스트는 토큰과 해당 게시판 배정을 검증하는 서버 API를 구현해야 한다.
34
+ Because of the Firestore read limit in Storage rules, the default rules let only the author and a global admin/owner modify or delete other users' files. Do not give staff/manager global file permissions. When staff/manager delete other users' posts, file cleanup uses the existing best-effort approach, so remaining files must be cleaned up by the host server. Host apps where assigned staff need to replace other users' images or attachments must implement a server API that verifies the token and the assignment to that board.
35
35
 
36
- 매니저는 공개 범위 변경·게시판 생성/삭제·자신의 매니저 배정 변경·전역 역할 변경을 할 수 없다. 담당 스태프 배정은 이미 staff/manager 자격을 가진 계정을 대상으로 한다.
36
+ Managers cannot change visibility, create or delete boards, change their own manager assignment, or change global roles. Assigned staff can only be chosen from accounts that already have staff/manager eligibility.
@@ -1,10 +1,10 @@
1
1
  // memi-board 0.45.1+ Storage Rules (flat schema, ownerUid metadata)
2
2
  // memiBoardPosts/{postId}/attachments|images|comment-images/...
3
- // postId 가 전역 고유이므로 boardId prefix 는 더 이상 필요 없음.
4
- // 부모/호스트 Firebase 프로젝트에서 Storage 서비스 계정에
5
- // roles/firebaserules.firestoreServiceAgent 역할을 설정해야 한다.
6
- // firestore.exists/get 조회 권한이 없으면 이미지·첨부 업로드·파일 삭제가 unauthorized로 실패한다.
7
- // 설정 및 확인 절차: docs/firebase-setup.md의 Storage 규칙의 Firestore 조회 권한 절.
3
+ // postId is globally unique, so the boardId prefix is no longer needed.
4
+ // In the host app's Firebase project, the Storage service account must be granted
5
+ // the roles/firebaserules.firestoreServiceAgent role.
6
+ // Without firestore.exists/get read access, image/attachment uploads and file deletes fail as unauthorized.
7
+ // Setup and verification: the "Firestore read access for Storage rules" section of docs/firebase-setup.md.
8
8
 
9
9
  rules_version = '2';
10
10
  service firebase.storage {
@@ -41,12 +41,12 @@ service firebase.storage {
41
41
  && firestore.get(postPath).data.authorUid == request.auth.uid;
42
42
  }
43
43
 
44
- // 글 삭제 시 패키지가 images/·attachments/·comment-images/ 를 listAll 한다.
45
- // 0.38.0 이하는 부모 경로 listAll(memiBoardPosts/{postId}) 를 쓴다.
46
- // 요청 접두사는 항상 memiBoardPosts/{postId}/ (끝 슬래시) 이라
47
- // match /memiBoardPosts/{postId} { allow list } 만으로는 403 이 난다.
48
- // {allPaths=**} 가 0칸 이상을 받아 폴더 목록과 파일 get/delete 에 필요하다.
49
- // write 는 여기에 넣지 않는다 — 용량·image/* 제한은 아래 세부 match 가 담당.
44
+ // When a post is deleted, the package runs listAll on images/, attachments/, and comment-images/.
45
+ // 0.38.0 and earlier use listAll on the parent path (memiBoardPosts/{postId}).
46
+ // The request prefix is always memiBoardPosts/{postId}/ (with a trailing slash), so
47
+ // match /memiBoardPosts/{postId} { allow list } alone results in a 403.
48
+ // {allPaths=**} matches zero or more segments and is needed for folder listing and file get/delete.
49
+ // Don't put write here — the size and image/* limits are handled by the more specific matches below.
50
50
  // Global admin maintenance lists the collection root to find abandoned uploads.
51
51
  match /memiBoardPosts/{allPaths=**} {
52
52
  allow list: if isBoardStaff();
@@ -67,9 +67,9 @@ service firebase.storage {
67
67
 
68
68
  match /memiBoardPosts/{postId}/images/{allPaths=**} {
69
69
  allow get: if true;
70
- // 글쓰기 화면은 Firestore 문서를 만들기 전에 Storage 경로(미리 딴 postId)로
71
- // 올린다. 붙여넣기 이미지는 image/png 등이 온다.
72
- // 기존 글은 작성자/스태프만 수정한다. 아직 저장하지 않은 파일은 ownerUid로 보호한다.
70
+ // The editor uploads to the Storage path (using a pre-generated postId) before creating the
71
+ // Firestore document. Pasted images arrive as image/png, etc.
72
+ // Existing posts can be modified only by the author/staff. Files not yet saved are protected by ownerUid.
73
73
  allow create, update: if canWritePostFile(postId)
74
74
  && request.resource != null
75
75
  && request.resource.size < 5 * 1024 * 1024
@@ -77,9 +77,9 @@ service firebase.storage {
77
77
  allow delete: if canDeletePostFile(postId);
78
78
  }
79
79
 
80
- // 댓글 이미지. 댓글 문서보다 먼저 올라오므로 여기서는 Firestore를 읽지 않는다.
81
- // 삭제는 올린 사람(ownerUid) 또는 글 작성자·전역 admin/owner.
82
- // 글 삭제는 글 문서가 남아 있는 동안 canDeletePostFile 로 이 폴더를 비운다.
80
+ // Comment images. These are uploaded before the comment document, so Firestore is not read here.
81
+ // Deletion: the uploader (ownerUid), or the post author / a global admin/owner.
82
+ // Post deletion empties this folder via canDeletePostFile while the post document still exists.
83
83
  match /memiBoardPosts/{postId}/comment-images/{commentId}/{fileName} {
84
84
  allow get: if true;
85
85
  allow create: if isSignedIn()
@@ -92,7 +92,7 @@ service firebase.storage {
92
92
  );
93
93
  }
94
94
 
95
- // 프로필 사진 — 경로가 uid로 고정돼 있어 본인만 쓸 수 있다. 공개 프로필의 일부라 읽기는 전체 공개.
95
+ // Profile photo — the path is fixed to the uid, so only the user can write. Reads are public since it is part of the public profile.
96
96
  match /memiBoardUsers/{uid}/avatar.jpg {
97
97
  allow get: if true;
98
98
  allow write: if isSignedIn()
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "memi-board",
3
- "version": "0.52.0",
3
+ "version": "0.52.2",
4
4
  "type": "module",
5
- "description": "Nuxt 4 + Nuxt UI + VueFire/Firebase 전용 게시판 모듈",
5
+ "description": "Board (forum) module for Nuxt 4 + Nuxt UI + VueFire/Firebase",
6
6
  "license": "MIT",
7
7
  "author": "fkkmemi",
8
8
  "repository": {