diff --git a/frontend/src/shared/constants/account-security.ts b/frontend/src/shared/constants/account-security.ts
index 8226983..9a50131 100644
--- a/frontend/src/shared/constants/account-security.ts
+++ b/frontend/src/shared/constants/account-security.ts
@@ -57,7 +57,7 @@ export const LOGIN_TWO_FACTOR_TEXT = {
codeLabel: '인증 앱에 표시된 6자리 코드',
submit: '확인',
submitting: '확인하는 중…',
- note: 'Outlook · 휴대폰 메일 앱은 이 코드를 묻지 않습니다. 설정 › 앱 비밀번호에서 만든 비밀번호로 연결합니다.',
+ note: 'Outlook이나 휴대폰 메일 앱은 이 코드를 묻지 않습니다. [설정] → [앱 비밀번호]에서 만든 비밀번호로 연결해 주세요.',
/** 코드 단계에 10분 넘게 머문 뒤 [확인] — 첫 단계의 위쪽 상자 */
expired: '코드를 넣는 시간이 지났습니다. 다시 로그인해 주세요.',
} as const
@@ -70,30 +70,30 @@ export const TWO_FACTOR_TEXT = {
off: '꺼짐',
offHint: '지금은 로그인할 때 비밀번호만 확인합니다.',
on: '켜짐',
- onSince: (day: string) => `켜짐 — ${day}부터`,
+ onSince: (day: string) => `${day}부터 켜짐`,
onHint: '로그인할 때마다 비밀번호와 인증 앱의 코드를 확인합니다.',
enable: '2단계 인증 켜기',
disable: '2단계 인증 끄기',
warningTitle: '켜면 메일 프로그램은 앱 비밀번호로 다시 연결해야 합니다',
/** 경고 글 — 가운데 「앱 비밀번호」는 앱 비밀번호 화면 링크 */
warningBefore:
- 'Outlook · 썬더버드 · 휴대폰 메일 앱은 웹메일 비밀번호로 새로 연결할 수 없고, 이미 연결해 둔 프로그램도 다음에 다시 연결할 때 막힙니다. 켜기 전에 ',
+ 'Outlook, 썬더버드, 휴대폰 메일 앱은 웹메일 비밀번호로 새로 연결할 수 없고, 이미 연결해 둔 프로그램도 다음에 다시 연결할 때 막힙니다. 켜기 전에 ',
warningLink: '앱 비밀번호',
warningAfter: '를 만들어 두면 편합니다.',
- stepsTitle: '켜는 차례',
+ stepsTitle: '켜는 방법',
steps: [
- '휴대폰에 인증 앱을 설치합니다 — Google Authenticator, Microsoft Authenticator 처럼 6자리 코드를 만드는(TOTP) 앱이면 됩니다.',
+ '휴대폰에 인증 앱을 설치합니다. Google Authenticator나 Microsoft Authenticator처럼 6자리 코드를 만드는 앱이면 됩니다.',
'[2단계 인증 켜기]를 누르고 화면의 QR 코드를 인증 앱으로 찍습니다.',
'앱에 표시된 6자리 코드와 지금 비밀번호를 넣으면 켜집니다. 그다음부터 로그인할 때마다 코드를 묻습니다.',
],
- noteChangePhone: '휴대폰을 바꿀 때는 2단계 인증을 끈 뒤 새 휴대폰으로 다시 켜 주세요 — 끌 때 지금 휴대폰의 코드가 필요합니다.',
+ noteChangePhone: '휴대폰을 바꿀 때는 2단계 인증을 끈 뒤 새 휴대폰으로 다시 켜 주세요. 끌 때 지금 휴대폰의 코드가 필요합니다.',
noteLostPhone: '휴대폰을 잃어버렸거나 인증 앱을 지웠으면 관리자에게 문의해 주세요.',
noteMailClientBefore: '메일 프로그램은 ',
noteMailClientLink: '앱 비밀번호',
noteMailClientAfter: '로 연결합니다.',
/** 켠 직후 머리 아래 안내 상자 — 이 화면을 떠나면 사라진다 */
enabledTitle: '2단계 인증을 켰습니다',
- enabledText: '메일 프로그램(Outlook · 썬더버드 · 휴대폰 메일 앱)은 다시 연결할 때 앱 비밀번호가 필요합니다.',
+ enabledText: 'Outlook, 썬더버드, 휴대폰 메일 앱 같은 메일 프로그램은 다시 연결할 때 앱 비밀번호가 필요합니다.',
enabledAction: '앱 비밀번호 만들기',
disabled: '2단계 인증을 껐습니다.',
} as const
@@ -102,17 +102,17 @@ export const TWO_FACTOR_TEXT = {
export const TWO_FACTOR_ENABLE_TEXT = {
title: '2단계 인증 켜기',
scanTitle: '인증 앱으로 QR 코드를 찍어 주세요',
- scanHint: '인증 앱에서 [+] → 「QR 코드 스캔」을 고르고 이 그림을 찍습니다.',
+ scanHint: '인증 앱에서 [+] → [QR 코드 스캔]을 고른 뒤 이 그림을 찍어 주세요.',
qrLabel: '인증 앱에 등록할 QR 코드',
- keyLabel: '찍을 수 없으면 이 키를 입력하세요',
+ keyLabel: '찍을 수 없으면 이 키를 입력해 주세요',
copy: '복사',
copyLabel: '키 복사',
copied: '키를 복사했습니다.',
copyFailed: '키를 복사하지 못했습니다. 키를 직접 골라 복사해 주세요.',
/** 앱에 저장될 이름 — 서버의 otpauthUrl 과 같은 이름(계약 5절 "정한 것" 1) */
- savedAs: (email: string) => `앱에는 「ARA SYSTEM (${email})」로 저장됩니다.`,
+ savedAs: (email: string) => `앱에는 ‘ARA SYSTEM (${email})’로 저장됩니다.`,
/** 켜지 않고 닫으면 그 키는 버린다 — 앱에 먼저 저장한 항목은 지워 달라고 안내한다 (README "켜기 창") */
- discardNote: '켜지 않고 닫으면 이 키는 버립니다 — 앱에 먼저 저장했다면 그 항목을 지워 주세요.',
+ discardNote: '켜지 않고 닫으면 이 키는 더 쓸 수 없습니다. 앱에 먼저 저장했다면 그 항목을 지워 주세요.',
loading: 'QR 코드를 만드는 중…',
codeLabel: '앱에 표시된 6자리 코드',
passwordLabel: '지금 비밀번호',
@@ -139,12 +139,12 @@ export const TWO_FACTOR_DISABLE_TEXT = {
export const APP_PASSWORD_TEXT = {
title: '앱 비밀번호',
description:
- 'Outlook · 썬더버드 · 휴대폰 메일 앱 같은 메일 프로그램에 넣는 비밀번호입니다. 프로그램마다 따로 만들어 두면, 기기를 잃었을 때 그것만 지울 수 있습니다.',
+ 'Outlook, 썬더버드, 휴대폰 메일 앱 같은 메일 프로그램에 넣는 비밀번호입니다. 프로그램마다 따로 만들어 두면 기기를 잃어버렸을 때 그 비밀번호만 지울 수 있습니다.',
count: (count: string, max: string) => `${count} / ${max}개`,
create: '앱 비밀번호 만들기',
/** 5개를 모두 만들었을 때 머리 아래 한 줄 — [만들기]를 눌러도 이 글을 읽어 준다 */
full: (max: string) => `${max}개까지 만들 수 있습니다. 쓰지 않는 앱 비밀번호를 지운 뒤 만들어 주세요.`,
- listLabel: '앱 비밀번호 — 최근에 만든 차례',
+ listLabel: '앱 비밀번호 목록, 최근에 만든 순서',
columnName: '이름',
columnCreated: '만든 날',
columnLastUsed: '마지막 사용',
@@ -154,20 +154,20 @@ export const APP_PASSWORD_TEXT = {
deleteLabel: (name: string) => `‘${name}’ 앱 비밀번호 지우기`,
emptyTitle: '만든 앱 비밀번호가 없습니다',
emptyHint:
- 'Outlook · 휴대폰 메일 앱에서 메일을 보려면 앱 비밀번호를 만들어 주세요. 2단계 인증을 켰다면 메일 프로그램은 앱 비밀번호로만 연결합니다.',
+ 'Outlook이나 휴대폰 메일 앱에서 메일을 보려면 앱 비밀번호를 만들어 주세요. 2단계 인증을 켰다면 메일 프로그램은 앱 비밀번호로만 연결할 수 있습니다.',
settingsTitle: '메일 프로그램 설정값',
incoming: '받는 메일 (IMAP)',
outgoing: '보내는 메일 (SMTP)',
username: '사용자 이름',
- usernameNote: ' — 메일 주소 전체',
+ usernameNote: ' · 메일 주소 전체',
password: '비밀번호',
- passwordNote: '여기서 만든 앱 비밀번호 — 웹메일 비밀번호가 아닙니다',
+ passwordNote: '웹메일 비밀번호가 아닌, 여기서 만든 앱 비밀번호',
port: (port: number) => `포트 ${port}`,
notes: [
- '앱 비밀번호는 메일 읽기 · 보내기(IMAP · SMTP)에만 쓰입니다. 웹메일 로그인에는 쓸 수 없습니다.',
+ '앱 비밀번호는 메일 프로그램에서 메일을 읽고 보낼 때만 씁니다. 웹메일 로그인에는 쓸 수 없습니다.',
'2단계 인증을 켜면 메일 프로그램은 앱 비밀번호로만 연결합니다. 켜지 않아도 만들어 쓸 수 있습니다.',
- '웹메일 비밀번호를 바꾸면 앱 비밀번호도 모두 지워집니다 — 메일 프로그램마다 새로 만들어 주세요.',
- '휴대폰이나 PC 를 잃어버렸으면 그 기기의 앱 비밀번호를 바로 지워 주세요.',
+ '웹메일 비밀번호를 바꾸면 앱 비밀번호도 모두 지워지니, 메일 프로그램마다 새로 만들어 주세요.',
+ '휴대폰이나 PC를 잃어버렸다면 그 기기의 앱 비밀번호를 바로 지워 주세요.',
],
deleted: (name: string) => `‘${name}’ 앱 비밀번호를 지웠습니다.`,
} as const
@@ -177,7 +177,7 @@ export const APP_PASSWORD_CREATE_TEXT = {
title: '앱 비밀번호 만들기',
nameLabel: '이름',
namePlaceholder: '예: 회사 PC 아웃룩',
- nameHelp: '어느 프로그램 · 기기에서 쓰는지 알아볼 이름입니다. 40자까지.',
+ nameHelp: '어느 프로그램이나 기기에서 쓰는지 알아볼 수 있게 지어 주세요. 40자까지 쓸 수 있습니다.',
empty: '이름을 넣어 주세요.',
duplicate: '같은 이름의 앱 비밀번호가 있습니다.',
submit: '만들기',
@@ -187,7 +187,7 @@ export const APP_PASSWORD_CREATE_TEXT = {
/** 만든 결과 창 (모달 620 · 처음 초점 [복사]) — 값은 이 창에서 한 번만 */
export const APP_PASSWORD_RESULT_TEXT = {
title: '앱 비밀번호를 만들었습니다',
- message: (name: string) => `‘${name}’ — 메일 프로그램의 비밀번호 칸에 아래 값을 넣어 주세요.`,
+ message: (name: string) => `‘${name}’ 앱 비밀번호입니다. 메일 프로그램의 비밀번호 칸에 아래 값을 넣어 주세요.`,
copy: '복사',
copyLabel: '앱 비밀번호 복사',
lengthNote: (length: string) => `${length}자입니다. 보기 좋게 4자씩 띄웠지만 띄어쓰기는 없습니다.`,
@@ -235,13 +235,13 @@ export const LOGIN_HISTORY_TEXT = {
unknownBrowser: '알 수 없는 브라우저',
unknownValue: '알 수 없음',
noOthers: '다른 곳에 로그인된 브라우저가 없습니다. ',
- sessionsNoteBefore: '웹메일 로그인만 보입니다. 메일 프로그램은 ',
+ sessionsNoteBefore: '웹메일 로그인만 보입니다. 메일 프로그램의 연결을 끊으려면 ',
sessionsNoteLink: '앱 비밀번호',
- sessionsNoteAfter: '를 지워 끊습니다.',
- sessionEnded: (device: string) => `‘${device}’의 로그인을 끊었습니다.`,
- othersEnded: (count: string) => `다른 곳의 로그인 ${count}곳을 끊었습니다.`,
+ sessionsNoteAfter: '를 지워 주세요.',
+ sessionEnded: (device: string) => `‘${device}’에서 로그아웃했습니다.`,
+ othersEnded: (count: string) => `다른 ${count}곳에서 로그아웃했습니다.`,
/** 서버가 이 브라우저를 알아보지 못했을 때(지금 이 브라우저 줄이 없음) — 이 브라우저까지 끊었다 */
- allEnded: (count: string) => `이 브라우저를 포함한 ${count}곳의 로그인을 끊었습니다.`,
+ allEnded: (count: string) => `이 브라우저를 포함해 ${count}곳에서 로그아웃했습니다.`,
eventsTitle: '최근 로그인',
eventsHintAll: '최근 90일 · 웹메일과 메일 프로그램',
eventsHintFailures: (count: string) => `최근 90일 · 실패 ${count}번`,
@@ -257,34 +257,34 @@ export const LOGIN_HISTORY_TEXT = {
methodSmtp: '메일 프로그램 · SMTP',
appPassword: (name: string) => `앱 비밀번호 ‘${name}’`,
success: '성공',
- successTwoFactor: '성공 · 2단계 인증',
+ successTwoFactor: '2단계 인증으로 성공',
failure: '실패',
- failureWrongPassword: '실패 — 비밀번호 틀림',
- failureWrongCode: '실패 — 코드 틀림',
- failureAppPasswordRequired: '실패 — 앱 비밀번호 필요',
+ failureWrongPassword: '비밀번호가 틀려 실패',
+ failureWrongCode: '코드가 틀려 실패',
+ failureAppPasswordRequired: '앱 비밀번호가 필요해 실패',
more: '더 보기',
- moreHint: '최근 순서 · 20개씩. 90일이 지난 기록은 지웁니다.',
+ moreHint: '최근 순서로 20개씩 보입니다. 90일이 지난 기록은 지워집니다.',
emptyFailuresTitle: '최근 90일 동안 실패한 로그인이 없습니다',
- emptyFailuresHint: '[전체]를 누르면 성공한 로그인도 봅니다.',
+ emptyFailuresHint: '[전체]를 누르면 성공한 로그인도 볼 수 있습니다.',
emptyAllTitle: '최근 90일 동안 로그인 기록이 없습니다',
- emptyAllHint: '기록은 이 기능을 배포한 날부터 쌓입니다.',
+ emptyAllHint: '이 기능이 생긴 날부터 기록이 쌓입니다.',
} as const
/** [다른 곳 모두 로그아웃] 확인 (alertdialog 400 · 처음 초점 「취소」) */
export const END_OTHER_SESSIONS_TEXT = {
- title: '다른 곳의 로그인을 모두 끊을까요?',
+ title: '다른 곳에서 모두 로그아웃할까요?',
/** "이 브라우저를 뺀 2곳(Edge · Windows, Safari · iOS)의 …" — 이름은 세 곳까지, 넘으면 "외 n곳" */
message: (count: string, names: string) =>
- `이 브라우저를 뺀 ${count}곳(${names})의 웹메일 로그인을 끊습니다. 끊긴 곳은 다음 동작에서 바로 로그인 화면으로 돌아갑니다.`,
+ `이 브라우저를 뺀 ${count}곳(${names})의 웹메일에서 로그아웃합니다. 그곳에서 다음에 무엇이든 누르면 바로 로그인 화면이 나옵니다.`,
/**
* 서버가 이 브라우저를 알아보지 못했을 때(지금 이 브라우저 줄이 없음 — 서버는 이 요청의 세션을 몰라 모두 끊는다, 계약 5절) —
* 이 브라우저도 로그아웃된다고 알린다
*/
- titleAll: '이 브라우저를 포함해 모든 곳의 로그인을 끊을까요?',
+ titleAll: '이 브라우저를 포함해 모든 곳에서 로그아웃할까요?',
messageAll: (count: string, names: string) =>
- `이 브라우저를 알아보지 못해 이 브라우저를 포함한 ${count}곳(${names})의 웹메일 로그인을 끊습니다. 이 브라우저도 로그아웃되어 로그인 화면으로 돌아갑니다.`,
+ `이 브라우저를 알아보지 못해서 이 브라우저를 포함한 ${count}곳(${names})의 웹메일에서 로그아웃합니다. 이 브라우저도 로그아웃되어 로그인 화면으로 돌아갑니다.`,
moreNames: (count: string) => `외 ${count}곳`,
- note: '메일 프로그램은 끊기지 않습니다 — 앱 비밀번호를 지워 끊어 주세요.',
+ note: '메일 프로그램은 로그아웃되지 않습니다. 연결을 끊으려면 앱 비밀번호를 지워 주세요.',
confirm: '모두 로그아웃',
cancel: '취소',
} as const
diff --git a/frontend/src/shared/constants/account.ts b/frontend/src/shared/constants/account.ts
index 00b7948..a012259 100644
--- a/frontend/src/shared/constants/account.ts
+++ b/frontend/src/shared/constants/account.ts
@@ -14,7 +14,7 @@ export const PASSWORD_CHANGE_TIMEOUT_MS = 30 * 1000
export const PASSWORD_TEXT = {
title: '비밀번호',
- description: '웹메일과 휴대폰 메일 앱(IMAP)이 함께 쓰는 메일 계정의 비밀번호입니다.',
+ description: '웹메일에 로그인할 때와 휴대폰 메일 앱을 연결할 때 함께 쓰는 비밀번호입니다.',
/** 2단계 인증을 켠 직원의 머리 설명 (12단계 — 디자인 원본 SettingsTwoFactor "비밀번호 — 2단계 인증을 켠 직원") */
descriptionTwoFactor: '웹메일에 로그인하는 비밀번호입니다. 2단계 인증이 켜져 있어 메일 프로그램은 앱 비밀번호로 연결합니다.',
current: '지금 비밀번호',
@@ -24,14 +24,14 @@ export const PASSWORD_TEXT = {
showCurrent: '지금 비밀번호 보기',
showNext: '새 비밀번호 보기',
/** 새 비밀번호 아래 안내 — 규칙에 맞지 않으면 이 글이 빨간 오류로 바뀐다 */
- hint: '8자 이상 · 지금 비밀번호와 달라야 합니다. 영문 · 숫자 · 기호를 섞으면 더 안전합니다.',
+ hint: '8자 이상이고 지금 비밀번호와 달라야 합니다. 영문, 숫자, 기호를 섞으면 더 안전합니다.',
mismatch: '새 비밀번호가 서로 다릅니다.',
/** 2단계 인증을 켠 직원만 — 양식 맨 아래 (코드가 30초마다 바뀌어 먼저 넣으면 다른 칸을 쓰는 동안 지날 수 있다) */
code: '인증 앱 코드',
- codeHint: '코드는 30초마다 바뀝니다 — 다른 칸을 다 쓴 뒤 넣어 주세요.',
+ codeHint: '코드는 30초마다 바뀌니 다른 칸을 다 채운 뒤에 넣어 주세요.',
submit: '비밀번호 바꾸기',
- done: '비밀번호를 바꿨습니다. 다른 기기 · 브라우저의 로그인은 끊었습니다.',
- noteSessions: '바꾸면 이 브라우저를 뺀 다른 기기 · 브라우저의 웹메일 로그인이 끊깁니다.',
+ done: '비밀번호를 바꿨습니다. 다른 기기와 브라우저에서는 로그아웃했습니다.',
+ noteSessions: '바꾸면 이 브라우저를 빼고 다른 기기와 브라우저의 웹메일에서 로그아웃됩니다.',
/** 모든 직원 (12단계) — 비밀번호를 바꾸면 서버가 그 직원의 앱 비밀번호를 모두 지운다 (계약 5절 "비밀번호 바꾸기") */
- noteAppPasswords: '앱 비밀번호도 모두 지워집니다 — 메일 프로그램은 새 앱 비밀번호로 다시 연결해 주세요.',
+ noteAppPasswords: '앱 비밀번호도 모두 지워지니, 메일 프로그램은 새 앱 비밀번호로 다시 연결해 주세요.',
} as const
diff --git a/frontend/src/shared/constants/contacts.ts b/frontend/src/shared/constants/contacts.ts
index a379334..0842ea2 100644
--- a/frontend/src/shared/constants/contacts.ts
+++ b/frontend/src/shared/constants/contacts.ts
@@ -1,5 +1,5 @@
// 주소록 상수 — 계약 3절 "주소록 — 8단계" · 디자인 README "8단계 — 주소록" (디자인 원본 Contacts · ContactsEdit · ContactsMail)
-// 한도는 서버와 같은 값이다 — 서버가 최종 검증한다. 문구는 보드의 글 그대로다(보드에 없던 것은 README "구현하며 정한 보완"으로 옮긴다).
+// 한도는 서버와 같은 값이다 — 서버가 최종 검증한다. 문구는 README "화면 문구 — 사용자에게 말하듯"(2026-11-05)을 따른다 — 보드의 옛 글과 다를 수 있다.
import type { ContactEmailLabel, ContactPhoneLabel, DirectoryKind } from '@/shared/types/contacts'
@@ -114,9 +114,9 @@ export const CONTACT_GROUP_MENU_TEXT = {
/** 상단 바의 찾기 칸 — 지금 보는 목록 안에서 찾는다 */
export const CONTACTS_SEARCH_TEXT = {
label: '주소록에서 찾기',
- placeholder: '이름 · 주소 · 회사 · 전화번호 — 초성도 됩니다 (ㅂㅈㅎ)',
+ placeholder: '이름, 주소, 회사, 전화번호, 초성(ㅂㅈㅎ)으로 찾기',
/** 사내 목록은 이름 · 주소로만 찾는다 (계약 3절 "찾기") */
- directoryPlaceholder: '이름 · 주소 — 초성도 됩니다 (ㅂㅈㅎ)',
+ directoryPlaceholder: '이름, 주소, 초성(ㅂㅈㅎ)으로 찾기',
clear: '찾을 글 지우기',
} as const
@@ -126,7 +126,7 @@ export const CONTACTS_LIST_TEXT = {
count: (count: string, shared: boolean) => `${count}${shared ? '개' : '명'}`,
found: (count: string, shared: boolean) => `찾은 결과 ${count}${shared ? '개' : '명'}`,
directoryNote: {
- person: '메일 서버에 계정이 있는 직원입니다 — 이름과 주소만 보입니다.',
+ person: '회사 메일을 쓰는 직원이라 이름과 메일 주소만 보입니다.',
shared: '여러 사람이 함께 받는 사내 주소입니다.',
} satisfies Record,
addMembers: '구성원 넣기',
@@ -142,11 +142,11 @@ export const CONTACTS_LIST_TEXT = {
/** 목록의 빈 상태 · 못 받음 (디자인 원본 Contacts · ContactsEdit "상태") */
export const CONTACTS_EMPTY_TEXT = {
searchTitle: (query: string, directory: boolean) => `‘${query}’에 맞는 ${directory ? '주소가' : '연락처가'} 없습니다`,
- searchHint: '이름 · 주소 · 회사 · 전화번호의 일부나 이름의 초성으로 찾습니다.',
- directorySearchHint: '이름 · 주소의 일부나 이름의 초성으로 찾습니다.',
+ searchHint: '이름, 주소, 회사, 전화번호의 일부나 이름의 초성으로 찾을 수 있습니다.',
+ directorySearchHint: '이름이나 주소의 일부, 이름의 초성으로 찾을 수 있습니다.',
firstTitle: '아직 연락처가 없습니다',
- firstHint: '자주 메일을 주고받는 사람을 넣어 두면 받는 사람 칸에서 바로 찾습니다.',
- firstNote: '메일을 읽다가 ⋯ › 「보낸 사람을 주소록에 추가」로도 넣을 수 있습니다.',
+ firstHint: '자주 메일을 주고받는 사람을 넣어 두면 받는 사람 칸에서 바로 찾을 수 있습니다.',
+ firstNote: '메일을 읽다가 [⋯] → [보낸 사람을 주소록에 추가]로도 넣을 수 있습니다.',
groupTitle: (name: string) => `‘${name}’ 그룹에 아직 아무도 없습니다`,
groupHint: '구성원을 넣으면 그룹 이름으로 한 번에 메일을 보낼 수 있습니다.',
directoryTitle: {
@@ -183,9 +183,9 @@ export const CONTACT_DETAIL_TEXT = {
/** 그룹 단추의 이름 — 누르면 그 그룹의 목록 */
openGroup: (name: string) => `‘${name}’ 그룹 보기`,
directoryHint:
- '사내 목록은 메일 서버의 계정에서 옵니다 — 이름과 주소만 보이고 여기서 고칠 수 없습니다. 전화번호 · 메모를 적어 두려면 내 주소록에 추가하세요.',
- noneTitle: '연락처를 고르세요',
- noneHint: '목록에서 고르면 주소 · 전화번호가 여기에 보입니다.',
+ '사내 목록은 이름과 주소만 보이고 여기서 고칠 수 없습니다. 전화번호나 메모를 적어 두려면 내 주소록에 추가해 주세요.',
+ noneTitle: '연락처를 골라 주세요',
+ noneHint: '목록에서 고르면 주소와 전화번호가 여기에 보입니다.',
leaveToList: '목록으로',
loading: '연락처 불러오는 중',
} as const
@@ -208,13 +208,13 @@ export const CONTACT_DIALOG_TEXT = {
emailAddress: (ordinal: string) => `${ordinal} 이메일`,
emailRemove: (ordinal: string) => `${ordinal} 이메일 빼기`,
addEmail: '이메일 더하기',
- emailHint: '5개까지 · 받는 사람 칸에는 맨 위 주소가 먼저 나옵니다',
+ emailHint: '5개까지 넣을 수 있고, 받는 사람 칸에는 맨 위 주소가 먼저 나옵니다.',
phone: '전화번호',
phoneKind: (ordinal: string) => `${ordinal} 전화번호의 종류`,
phoneNumber: (ordinal: string) => `${ordinal} 전화번호`,
phoneRemove: (ordinal: string) => `${ordinal} 전화번호 빼기`,
addPhone: '전화번호 더하기',
- phoneHint: '5개까지',
+ phoneHint: '5개까지 넣을 수 있습니다.',
company: '회사',
department: '부서',
title: '직책',
@@ -228,15 +228,15 @@ export const CONTACT_DIALOG_TEXT = {
emailsTooMany: (max: string) => `이메일은 ${max}개까지 넣을 수 있습니다. 몇 개를 빼 주세요.`,
phonesTooMany: (max: string) => `전화번호는 ${max}개까지 넣을 수 있습니다. 몇 개를 빼 주세요.`,
groups: '그룹',
- required: '이름이나 이메일 가운데 하나는 있어야 합니다.',
+ required: '이름이나 이메일 가운데 하나는 넣어 주세요.',
save: '저장',
cancel: '취소',
remove: '이 연락처 지우기',
emailInvalid: '이메일 형식이 올바르지 않습니다.',
emailRepeated: '같은 주소를 두 번 넣었습니다.',
- phoneInvalid: '전화번호 형식이 올바르지 않습니다. 숫자와 띄어쓰기 · + - ( ) . # * 만 쓸 수 있습니다.',
+ phoneInvalid: '전화번호 형식이 올바르지 않습니다. 숫자와 띄어쓰기, + - ( ) . # * 기호만 쓸 수 있습니다.',
/** 같은 주소가 이미 주소록에 있다 — 막지 않는다 (who: "강서윤(거래처)") */
- duplicate: (who: string) => `이미 주소록에 있는 주소입니다 — ${who}. 그래도 저장할 수 있습니다.`,
+ duplicate: (who: string) => `주소록에 있는 ${who}의 주소와 같습니다. 그래도 저장할 수 있습니다.`,
duplicateView: '그 연락처 보기',
} as const
@@ -273,8 +273,8 @@ export const CONTACT_MEMBERS_TEXT = {
mine: '내 연락처',
staff: '사내 직원',
search: '구성원으로 넣을 연락처 찾기',
- searchPlaceholder: '이름 · 주소 — 초성도 됩니다',
- note: '이미 이 그룹에 있는 연락처는 보이지 않습니다. 「사내 직원」에서 고르면 그 직원을 내 주소록에도 추가합니다(이름 · 주소).',
+ searchPlaceholder: '이름, 주소, 초성으로 찾기',
+ note: '이미 이 그룹에 있는 연락처는 보이지 않습니다. [사내 직원]에서 고르면 그 직원의 이름과 주소를 내 주소록에도 추가합니다.',
picked: (count: string) => `${count}명 고름`,
pickMax: '한 번에 100명까지 넣을 수 있습니다.',
add: '넣기',
@@ -294,7 +294,7 @@ export const CONTACTS_TOAST_TEXT = {
groupCreated: (name: string) => `‘${name}’ 그룹을 만들었습니다.`,
groupRenamed: '그룹 이름을 바꿨습니다.',
groupRemoved: (name: string, count: string | null) =>
- count === null ? `‘${name}’ 그룹을 지웠습니다.` : `‘${name}’ 그룹을 지웠습니다 — 연락처 ${count}명은 그대로 있습니다.`,
+ count === null ? `‘${name}’ 그룹을 지웠습니다.` : `‘${name}’ 그룹을 지웠습니다. 그룹에 있던 연락처 ${count}명은 그대로 남아 있습니다.`,
groupRestored: '그룹을 되돌렸습니다.',
/** 응답의 addedCount — 고른 수와 다를 수 있다(이미 구성원 · 그룹에 넣을 수 없는 카드는 서버가 건너뛴다) */
membersAdded: (count: string, group: string) => `${count}명을 ‘${group}’ 그룹에 넣었습니다.`,
@@ -317,7 +317,7 @@ export const CONTACT_READER_TEXT = {
/** 받는 사람 추천의 주소록 · 그룹 (디자인 원본 ContactsMail "받는 사람 추천") */
export const RECIPIENT_CONTACT_TEXT = {
/** 그룹 줄의 설명 */
- groupDetail: (count: string) => `${count}명 · 고르면 한꺼번에 넣습니다`,
+ groupDetail: (count: string) => `고르면 ${count}명을 한꺼번에 넣습니다`,
groupLoading: (group: string) => `‘${group}’ 그룹의 구성원을 불러오는 중…`,
groupAdded: (group: string, count: string, field: string) => `‘${group}’ 그룹의 ${count}명을 ${field}에 넣었습니다.`,
groupNone: (group: string, field: string) => `‘${group}’ 그룹에서 ${field}에 더 넣을 주소가 없습니다.`,
diff --git a/frontend/src/shared/constants/large-file.ts b/frontend/src/shared/constants/large-file.ts
index 7ab978f..7c085f3 100644
--- a/frontend/src/shared/constants/large-file.ts
+++ b/frontend/src/shared/constants/large-file.ts
@@ -81,13 +81,13 @@ export const SHARE_SCAN_POLL_LIMIT = 20
// ── 화면 글 — 쓰기 화면 (디자인 원본 ComposeLargeFiles) ─────────────
export const LARGE_FILE_TEXT = {
- /** 첨부 안내 — "파일당 최대 20MB · 합계 25MB · 최대 20개 · 더 큰 파일은 링크로 보냅니다 (파일당 2GB · 10개까지)" */
- hintTail: (maxFile: string, maxCount: string) => `더 큰 파일은 링크로 보냅니다 (파일당 ${maxFile} · ${maxCount}개까지)`,
+ /** 첨부 안내 — "파일당 최대 20MB · 합계 25MB · 최대 20개 · 더 큰 파일은 파일당 2GB, 10개까지 링크로 보냅니다" */
+ hintTail: (maxFile: string, maxCount: string) => `더 큰 파일은 파일당 ${maxFile}, ${maxCount}개까지 링크로 보냅니다`,
normalGroup: (count: string) => `메일에 담는 파일 ${count}개`,
normalTotal: (used: string, limit: string) => `합계 ${used} / ${limit}`,
linkGroup: (count: string) => `링크로 보내는 파일 ${count}개`,
linkGroupHint: (days: string, until: string) =>
- `받는 사람은 보낸 날부터 ${days}일 동안 내려받습니다 — 오늘 보내면 ${until}까지`,
+ `받는 사람은 보낸 날부터 ${days}일 동안 내려받을 수 있습니다. 오늘 보내면 ${until}까지입니다.`,
/** 보관 사용량이 80%를 넘을 때 링크 묶음 머리 오른쪽 */
quotaWarning: (usage: string) => `보관 ${usage}`,
/** 합계 때문에 저절로 링크로 바꿨을 때 — 칸 아래 ⓘ 한 줄 (role=status) */
@@ -111,22 +111,22 @@ export const LARGE_FILE_TEXT = {
queued: '차례를 기다리는 중',
reconnecting: (progress: string) => `연결이 끊겨 다시 잇는 중… · ${progress} 올림`,
paused: (progress: string) => `멈춤 · ${progress} 올림`,
- stalled: (uploaded: string) => `올리지 못하고 멈췄습니다 — 올린 ${uploaded} 는 남아 있습니다.`,
+ stalled: (uploaded: string) => `올리지 못하고 멈췄습니다. 지금까지 올린 ${uploaded}는 남아 있습니다.`,
stalledEmpty: '올리지 못하고 멈췄습니다.',
waitingFile: (progress: string) =>
- `올리다 멈춘 파일 — 같은 파일을 다시 고르면 이어서 올립니다 · ${progress} 올림`,
- repickMismatch: '고른 파일이 멈춘 파일과 다릅니다(이름 · 크기). 같은 파일을 골라 주세요.',
- scanning: (size: string) => `${size} · 보안 검사 중 — 끝나지 않아도 보낼 수 있습니다`,
- unscannable: (size: string) => `${size} · 검사하지 못함 — 암호가 걸린 압축 파일 등`,
+ `올리다 멈춤 · ${progress} 올림 · 같은 파일을 다시 고르면 이어서 올립니다`,
+ repickMismatch: '고른 파일의 이름이나 크기가 멈춘 파일과 다릅니다. 같은 파일을 골라 주세요.',
+ scanning: (size: string) => `${size} · 보안 검사 중이지만 보낼 수 있습니다`,
+ unscannable: (size: string) => `${size} · 암호가 걸린 압축 파일처럼 검사하지 못한 파일`,
keptInDraft: (size: string, until: string) => `${size} · 이 임시 저장본에 ${until}까지 남습니다`,
infected: '위험한 내용이 발견되어 보낼 수 없습니다. 이 파일을 빼 주세요.',
- missingExpired: '기간이 지나 지운 파일입니다 — 빼고 다시 올려 주세요.',
- missingDeleted: '설정 › 대용량 첨부에서 지운 파일입니다 — 빼고 다시 올려 주세요.',
- missingUnknown: '서버에서 찾을 수 없는 파일입니다 — 빼고 다시 올려 주세요.',
+ missingExpired: '기간이 지나 지워진 파일입니다. 빼고 다시 올려 주세요.',
+ missingDeleted: '[설정] → [대용량 첨부]에서 지운 파일입니다. 빼고 다시 올려 주세요.',
+ missingUnknown: '파일을 찾을 수 없습니다. 빼고 다시 올려 주세요.',
tooLarge: (max: string) => `파일 하나는 ${max} 까지 보낼 수 있습니다.`,
tooMany: (count: string) => `링크로 보내는 파일은 메일 한 통에 ${count}개까지입니다.`,
overQuota: (limit: string) =>
- `대용량 보관 한도(${limit})를 넘어 올릴 수 없습니다. 보내지 않은 파일을 지우거나 링크를 끊으면 바로 공간이 생깁니다.`,
+ `대용량 첨부는 ${limit}까지 보관할 수 있어 더 올릴 수 없습니다. 보내지 않은 파일을 지우거나 링크를 끊으면 바로 공간이 생깁니다.`,
quotaLink: '대용량 첨부 보기',
unnamed: '(이름 없는 파일)',
/** 화면 낭독기 — 10% 마다 */
@@ -136,21 +136,21 @@ export const LARGE_FILE_TEXT = {
/** [멈춤] 단추 이름 — "설계도_v3.dwg 올리기 멈춤" */
pauseLabel: (name: string) => `${name} 올리기 멈춤`,
/** 서버가 올리던 파일을 잊었다(404 — 마지막 조각 뒤 24시간 · 설정에서 지움) — 멈춘 줄 글. [다시 시도]는 처음부터 새로 올린다 */
- restartNeeded: '서버에서 올리던 파일을 찾을 수 없습니다 — 다시 시도하면 처음부터 올립니다.',
+ restartNeeded: '올리던 파일을 찾을 수 없습니다. [다시 시도]를 누르면 처음부터 올립니다.',
} as const
/** [보내기]를 막는 까닭 — 단추 옆 글 (디자인 README "11단계 › 다 올릴 때까지 보내기를 막는다") */
export const SEND_BLOCK_TEXT = {
- uploading: (count: string) => `파일 ${count}개를 올리는 중입니다 — 다 올리면 보낼 수 있습니다.`,
- incomplete: '다 올리지 못한 파일이 있습니다 — 이어 올리거나 빼 주세요.',
- unsendable: '보낼 수 없는 파일이 있습니다 — 빨간 줄의 파일을 빼 주세요.',
- tooMany: (count: string) => `링크로 보내는 파일은 메일 한 통에 ${count}개까지입니다 — 빼 주세요.`,
+ uploading: (count: string) => `파일 ${count}개를 올리는 중입니다. 다 올린 뒤에 보낼 수 있습니다.`,
+ incomplete: '다 올리지 못한 파일이 있습니다. 이어 올리거나 빼 주세요.',
+ unsendable: '보낼 수 없는 파일이 있습니다. 빨간 줄의 파일을 빼 주세요.',
+ tooMany: (count: string) => `링크로 보내는 파일은 메일 한 통에 ${count}개까지입니다. 몇 개를 빼 주세요.`,
} as const
/** 인용 속 원래 링크의 상태 — 첨부 칸 아래 글자로만 (디자인 README "11단계 › 읽기 화면 › 전달 쓰기 화면") */
export const QUOTED_LINK_TEXT = {
- active: (count: string, until: string) => `원래 메일의 대용량 첨부 ${count}개는 인용 속 링크로 함께 갑니다 — ${until}까지`,
- activeUnknownCount: (until: string) => `인용 속 대용량 첨부 링크는 함께 갑니다 — ${until}까지`,
+ active: (count: string, until: string) => `원래 메일의 대용량 첨부 ${count}개는 인용 속 링크로 함께 갑니다. ${until}까지 받을 수 있습니다.`,
+ activeUnknownCount: (until: string) => `인용 속 대용량 첨부 링크도 함께 갑니다. ${until}까지 받을 수 있습니다.`,
revoked: (label: string) => `인용 속 ${label} 링크는 공유가 멈춰 받을 수 없습니다.`,
expired: (label: string) => `인용 속 ${label} 링크는 기간이 지나 받을 수 없습니다.`,
/** 파일 이름을 모를 때 */
@@ -172,18 +172,18 @@ export const MESSAGE_LARGE_FILE_TEXT = {
until: (date: string) => `${date}까지`,
downloads: (count: string) => `${count}회 받음`,
notDownloaded: '아직 받지 않음',
- scanning: '보안 검사 중 — 끝나면 받을 수 있습니다',
- infected: '위험 — 보안 검사에서 막았습니다. 받을 수 없습니다',
- unscannable: '검사하지 못함 — 암호가 걸린 압축 파일 등',
- expired: (date: string) => `기간 지남 — ${date}까지였습니다`,
- revoked: '공유 멈춤 — 보낸 사람이 링크를 끊었습니다',
+ scanning: '보안 검사가 끝나면 받을 수 있습니다',
+ infected: '위험한 내용이 있어 보안 검사에서 막았고, 받을 수 없습니다',
+ unscannable: '암호가 걸린 압축 파일처럼 검사하지 못한 파일',
+ expired: (date: string) => `받을 수 있는 기간이 ${date}에 끝났습니다`,
+ revoked: '보낸 사람이 링크를 끊어 받을 수 없습니다',
/** 보낸 사람 자신 — 끊은 날을 알면 그 날짜(디자인 원본 MailReadLargeFiles 셋째 줄), 모르면 날짜 없이 */
- revokedOwn: (date: string | null) => (date ? `공유 멈춤 — ${date}에 끊었습니다` : '공유 멈춤 — 링크를 끊었습니다'),
- limitReached: '횟수 초과 — 더 내려받을 수 없습니다',
- missing: '찾을 수 없음 — 더 내려받을 수 없습니다',
+ revokedOwn: (date: string | null) => (date ? `${date}에 링크를 끊었습니다` : '링크를 끊었습니다'),
+ limitReached: '받을 수 있는 횟수를 넘어 더 내려받을 수 없습니다',
+ missing: '파일을 찾을 수 없어 더 내려받을 수 없습니다',
download: (name: string) => `${name} 다운로드`,
/** 보낸 사람 자신 — 첨부 칸 아래 한 줄 */
- linkNote: (count: string) => `링크로 보낸 파일 ${count}개 — 받는 사람 모두 같은 링크로 받습니다.`,
+ linkNote: (count: string) => `링크로 보낸 파일 ${count}개는 받는 사람 모두 같은 링크로 받습니다.`,
linkNoteCut: (count: number) =>
count === 1 ? '끊으면 받을 수 없습니다.' : count === 2 ? '끊으면 둘 다 받을 수 없습니다.' : '끊으면 모두 받을 수 없습니다.',
copyLink: '링크 복사',
@@ -192,7 +192,7 @@ export const MESSAGE_LARGE_FILE_TEXT = {
revoke: '링크 끊기',
/** 본문의 덩이를 접는 줄 (본문 틀 안 — 스크립트 없이 details · summary) */
foldLabel: '본문의 대용량 첨부 안내',
- foldHint: '(접어 둠) — 파일은 아래 첨부 칸에 있습니다',
+ foldHint: '파일은 아래 첨부 칸에 있어 접어 두었습니다',
foldOpen: '펼치기',
foldClose: '접기',
} as const
@@ -206,7 +206,7 @@ export const LARGE_FILE_REVOKE_TEXT = {
/** 설정 — 메일 제목으로 */
messageWithSubject: (subject: string, count: string) =>
`‘${subject}’의 파일 ${count}개를 받는 사람 모두 더는 내려받을 수 없습니다. 이미 받은 파일은 되돌릴 수 없고, 끊은 링크는 다시 열 수 없습니다.`,
- nameSeparator: ' · ',
+ nameSeparator: ', ',
confirm: '링크 끊기',
cancel: '취소',
done: '링크를 끊었습니다. 받는 사람은 더 이상 내려받을 수 없습니다.',
@@ -224,16 +224,16 @@ export const LARGE_FILE_SETTINGS_TEXT = {
description: (days: string) =>
`메일에 링크로 보낸 큰 파일입니다. 받는 사람은 보낸 날부터 ${days}일 동안 내려받고, 링크를 끊으면 바로 받을 수 없습니다.`,
usageLabel: '보관 사용량',
- usageSplit: (sent: string, unsent: string) => `보낸 링크 ${sent} · 보내지 않은 파일 ${unsent}(임시 저장 · 올리다 멈춤).`,
+ usageSplit: (sent: string, unsent: string) => `보낸 링크 ${sent}, 임시 저장했거나 올리다 멈춘 파일 ${unsent}입니다.`,
usageNote: '기간이 지나거나, 링크를 끊거나, 보내지 않은 파일을 지우면 바로 줄어듭니다.',
unsentTitle: (count: string) => `보내지 않은 파일 ${count}개`,
- unsentHint: (size: string) => `${size} · 임시 저장한 메일에 실렸거나 올리다 멈춘 파일 — 지우면 바로 공간이 생깁니다`,
+ unsentHint: (size: string) => `${size} · 임시 저장한 메일에 실렸거나 올리다 멈춘 파일이라 지우면 바로 공간이 생깁니다`,
unsentList: '보내지 않은 파일',
unsentUploading: '올리다 멈춤',
unsentReady: '다 올림',
unsentScanning: '보안 검사 중',
unsentUnscannable: '검사하지 못함',
- unsentInfected: '위험 — 보낼 수 없음',
+ unsentInfected: '위험해서 보낼 수 없음',
partUploaded: (bytes: string) => `${bytes} 올림`,
remainUntil: (when: string) => `${when}까지 남음`,
afterLastChunk: '마지막으로 올린 뒤 24시간',
@@ -241,12 +241,12 @@ export const LARGE_FILE_SETTINGS_TEXT = {
deleteLabel: (name: string) => `${name} 지우기`,
deleteTitle: '보내지 않은 파일을 지울까요?',
deleteMessage: (name: string, size: string) =>
- `‘${name}’(${size}) 파일을 지웁니다. 이 파일을 실은 임시 저장본이 있으면 열 때 첨부 칸에 ‘지운 파일’로 보입니다. 지운 파일은 되돌릴 수 없습니다.`,
+ `‘${name}’(${size}) 파일을 지웁니다. 이 파일을 실은 임시 저장본을 열면 첨부 칸에 지운 파일로 보입니다. 지운 파일은 되돌릴 수 없습니다.`,
deleteConfirm: '지우기',
deleteCancel: '취소',
deleted: (name: string) => `‘${name}’ 파일을 지웠습니다.`,
linksTitle: (count: string) => `보낸 링크 ${count}개`,
- linksHint: '메일마다 한 덩이 · 최근에 보낸 차례',
+ linksHint: '메일마다 하나씩, 최근에 보낸 순서',
linksList: '링크로 보낸 메일',
sentLine: (day: string, count: string, total: string) => `${day} 보냄 · 파일 ${count}개 · ${total}`,
until: (date: string) => `${date}까지`,
@@ -269,7 +269,7 @@ export const LARGE_FILE_SETTINGS_TEXT = {
notDownloaded: '아직 받지 않음',
lastDownloaded: (when: string) => `마지막 ${when}`,
more: '더 보기',
- moreHint: '최근에 보낸 차례 · 20개씩. 끝난 링크는 90일 뒤 목록에서 빠집니다.',
+ moreHint: '최근에 보낸 순서로 20개씩 보입니다. 끝난 링크는 90일 뒤 목록에서 빠집니다.',
emptyTitle: '링크로 보낸 파일이 없습니다',
emptyHint: (max: string, days: string) =>
`메일 쓰기에서 ${max} 가 넘는 파일을 고르면 링크로 보냅니다. 받는 사람은 보낸 날부터 ${days}일 동안 내려받을 수 있습니다.`,
@@ -279,31 +279,33 @@ export const LARGE_FILE_SETTINGS_TEXT = {
// ── 화면 글 — 공개 내려받기 화면 (디자인 원본 SharePage) ─────────────
export const SHARE_PAGE_TEXT = {
barTitle: '파일 내려받기',
- /** 탭 제목 — "김아라 님이 보낸 파일 — ARA SYSTEM" */
- tabTitle: (sender: string, app: string) => `${sender} 님이 보낸 파일 — ${app}`,
- tabTitleDefault: (app: string) => `파일 내려받기 — ${app}`,
+ /** 탭 제목 — "김아라 님이 보낸 파일 · ARA SYSTEM" (앱의 다른 탭 제목과 같은 가운뎃점) */
+ tabTitle: (sender: string, app: string) => `${sender} 님이 보낸 파일 · ${app}`,
+ tabTitleDefault: (app: string) => `파일 내려받기 · ${app}`,
title: (sender: string, count: string, total: string) => `${sender} 님이 보낸 파일 ${count}개 (${total})`,
senderLabel: '보낸 사람',
+ /** 기한 줄 — "한국 시간으로 **2026년 11월 29일(일)까지** 내려받을 수 있습니다. 30일 남았습니다." (날짜 조각만 굵게) */
+ untilBefore: '한국 시간으로 ',
until: (date: string) => `${date}까지`,
- untilTail: (days: string) => ` 내려받을 수 있습니다 (한국 시간) · ${days}일 남음`,
- untilTailLastDay: ' 내려받을 수 있습니다 (한국 시간) · 오늘까지',
- clean: '보안 검사 · 이상 없음',
+ untilTail: (days: string) => ` 내려받을 수 있습니다. ${days}일 남았습니다.`,
+ untilTailLastDay: ' 내려받을 수 있습니다. 오늘이 마지막 날입니다.',
+ clean: '보안 검사 결과 이상 없음',
scanning: '보안 검사 중',
- unscannable: '검사하지 못함 — 암호가 걸린 압축 파일 등 안을 볼 수 없는 파일입니다. 보낸 사람을 아는 경우에만 여세요.',
- infected: '위험 — 막았습니다',
+ unscannable: '암호가 걸린 압축 파일처럼 안을 볼 수 없어 검사하지 못한 파일입니다. 보낸 사람을 아는 경우에만 열어 주세요.',
+ infected: '위험한 내용이 있어 막았습니다',
limitReached: '내려받을 수 있는 횟수를 넘었습니다. 보낸 사람에게 다시 보내 달라고 요청해 주세요.',
- missing: '찾을 수 없는 파일입니다 — 더 내려받을 수 없습니다.',
+ missing: '파일을 찾을 수 없어 더 내려받을 수 없습니다.',
download: '내려받기',
downloadLabel: (name: string) => `${name} 내려받기`,
- resumeHint: '내려받다 끊기면 브라우저의 다운로드 목록에서 다시 시도하세요 — 받은 곳부터 이어서 받습니다.',
+ resumeHint: '내려받다 끊기면 브라우저의 다운로드 목록에서 다시 시도해 주세요. 받은 곳부터 이어서 받습니다.',
noLogin: '이 화면은 로그인이나 비밀번호를 묻지 않습니다. 묻는 화면이 나오면 닫고 보낸 사람에게 확인해 주세요.',
reportBefore: '받을 이유가 없는 파일이면 ',
reportAfter: ' 로 알려 주세요.',
englishActive: (sender: string, email: string, until: string) =>
- `Files from ${sender} (${email}) — download by ${until} (KST). This page never asks for a login or password.`,
+ `Files from ${sender} (${email}). Download by ${until} (KST). This page will never ask you to log in or enter a password.`,
scanningTitle: '보안 검사 중입니다',
scanningMessage: '파일을 검사하고 있습니다. 몇 분 뒤 이 화면을 다시 열어 주세요.',
- scanningEnglish: 'Security scan in progress — please check again in a few minutes.',
+ scanningEnglish: 'A security scan is in progress. Please check again in a few minutes.',
recheck: '다시 확인',
infectedTitle: '내려받을 수 없는 파일입니다',
infectedMessage: '보안 검사에서 위험한 내용이 발견되어 막았습니다. 보낸 사람에게 알렸습니다.',
@@ -314,16 +316,16 @@ export const SHARE_PAGE_TEXT = {
expiredEnglish: (date: string) => `This link expired on ${date} (KST).`,
revokedTitle: '보낸 사람이 공유를 멈췄습니다',
revokedMessage: (sender: string) => `필요하면 보낸 사람(${sender})에게 문의해 주세요.`,
- revokedEnglish: 'The sender stopped sharing these files.',
+ revokedEnglish: 'The sender has stopped sharing these files.',
notFoundTitle: '찾을 수 없는 링크입니다',
notFoundMessage: '메일의 주소가 끝까지 복사되었는지 확인해 주세요.',
- notFoundEnglish: 'Link not found — check that the whole address was copied.',
+ notFoundEnglish: 'Link not found. Please check that the full address was copied.',
tooManyTitle: '요청이 너무 많습니다',
tooManyMessage: '잠시 뒤 다시 시도해 주세요.',
- tooManyEnglish: 'Too many requests — please try again shortly.',
+ tooManyEnglish: 'Too many requests. Please try again in a moment.',
errorTitle: '화면을 불러오지 못했습니다',
errorMessage: '잠시 뒤 다시 시도해 주세요.',
- errorEnglish: 'Something went wrong — please try again shortly.',
+ errorEnglish: 'Something went wrong. Please try again in a moment.',
retry: '다시 시도',
/** 누르기 직전에 다시 물었더니 받을 수 없게 됐다 (role=status) */
unavailableNow: '이 파일은 지금 내려받을 수 없습니다. 아래 안내를 확인해 주세요.',
diff --git a/frontend/src/shared/constants/mail-block.ts b/frontend/src/shared/constants/mail-block.ts
index 6bb8db9..7806496 100644
--- a/frontend/src/shared/constants/mail-block.ts
+++ b/frontend/src/shared/constants/mail-block.ts
@@ -56,7 +56,7 @@ export const MAIL_BLOCK_TEXT = {
// ── 차단 확인 창 ──
dialogTitle: '보낸 사람 차단',
- dialogMessage: '앞으로 오는 메일을 스팸메일함으로 보냅니다. 부재중 자동 응답도 보내지 않습니다.',
+ dialogMessage: '앞으로 오는 메일은 스팸메일함으로 옮겨지고, 부재중 자동 응답도 보내지 않습니다.',
scopeGroup: '차단할 범위',
scopeAddress: '이 주소만',
scopeDomain: '이 도메인 전체',
@@ -65,7 +65,7 @@ export const MAIL_BLOCK_TEXT = {
/** 「이 도메인 전체」를 끈 까닭 — 여러 사람이 쓰는 메일 서비스 (보드에 없다) */
scopeDomainPublic: '여러 사람이 쓰는 메일 서비스라 도메인으로 막을 수 없습니다.',
moveToJunk: '이 메일도 스팸메일함으로 옮기기',
- settingsHint: '차단한 주소는 설정 › 메일 › 차단한 주소에서 풀 수 있습니다.',
+ settingsHint: '차단은 [설정] → [차단한 주소]에서 풀 수 있습니다.',
confirm: '차단',
cancel: '취소',
@@ -90,23 +90,23 @@ export const MAIL_BLOCK_TEXT = {
/** "3 / 500" */
count: (count: string, max: string) => `${count} / ${max}`,
description:
- '이 주소에서 오는 메일은 스팸메일함으로 갑니다. 부재중 자동 응답도 보내지 않습니다. 「@도메인」은 그 도메인에서 오는 메일 모두입니다.',
+ '차단한 주소에서 온 메일은 스팸메일함으로 옮겨지고, 부재중 자동 응답도 보내지 않습니다. @spam.example 처럼 @로 시작해 넣으면 그 도메인에서 오는 메일을 모두 막습니다.',
addLabel: '차단할 주소',
addPlaceholder: 'kim@example.com 또는 @example.com',
add: '차단',
/** 칸 아래 오류 */
invalid: '메일 주소나 @도메인 형식으로 써 주세요.',
duplicate: '이미 차단한 주소입니다.',
- own: '내 주소 · 사내 도메인은 차단할 수 없습니다.',
- publicDomain: 'gmail.com 처럼 여러 사람이 쓰는 메일 서비스는 도메인으로 막을 수 없습니다 — 주소로 막아 주세요.',
+ own: '내 주소와 사내 도메인은 차단할 수 없습니다.',
+ publicDomain: 'gmail.com처럼 여러 사람이 쓰는 메일 서비스는 도메인 전체를 막을 수 없습니다. 주소를 하나씩 넣어 막아 주세요.',
full: `${MAIL_BLOCK_MAX}개까지 차단할 수 있습니다.`,
/** 목록 줄 아래 작은 글 — "10월 29일 차단" · "도메인 전체 · 10월 21일 차단" */
blockedOn: (day: string) => `${day} 차단`,
domainBlockedOn: (day: string) => `도메인 전체 · ${day} 차단`,
- empty: '차단한 주소가 없습니다. 메일의 ⋯ › 보낸 사람 차단으로도 막을 수 있습니다.',
+ empty: '차단한 주소가 없습니다. 메일을 읽다가 [⋯] → [보낸 사람 차단]으로도 막을 수 있습니다.',
/**
* 머리 아래 노란 줄 — 차단한 주소가 있는데 엔진에서 우리 규칙이 꺼져 있을 때(isActive: false). 단추는 없다 — 차단하거나 풀면 다시 켜진다
* (보드에 없다 — 7단계 필터 · 부재중 자동 응답의 노란 줄과 같은 결)
*/
- inactive: '지금은 차단이 동작하지 않습니다. 메일 서버의 규칙이 다른 곳에서 바뀌었습니다 — 주소를 차단하거나 풀면 다시 켜집니다.',
+ inactive: '다른 곳에서 메일 설정이 바뀌어 지금은 차단이 되지 않습니다. 주소를 차단하거나 풀면 다시 켜집니다.',
} as const
diff --git a/frontend/src/shared/constants/mail-clients.ts b/frontend/src/shared/constants/mail-clients.ts
index c35115d..97415be 100644
--- a/frontend/src/shared/constants/mail-clients.ts
+++ b/frontend/src/shared/constants/mail-clients.ts
@@ -1,6 +1,6 @@
// 설정 › 계정 › 메일 프로그램 연결 (13단계 — 계약 6절 "메일 프로그램 연결", 디자인 README "13단계 › 설정 › 계정 › 메일 프로그램 연결")
// API 는 없다 — 이 상수로만 그린다. 설정값(호스트 · 포트)은 앱 비밀번호 화면과 같은 MAIL_CLIENT_SETTINGS 하나.
-// 차례 글은 README 그대로다 — 누르는 것 [ ], 고르는 것 「 」, 호스트 이름은 굵게. 판에 따라 메뉴 이름이 조금 다를 수 있다.
+// 차례 글은 README 그대로다 — 누르거나 고르는 것은 [ ], 호스트 이름은 굵게(README "화면 문구 — 사용자에게 말하듯"). 판에 따라 메뉴 이름이 조금 다를 수 있다.
import { MAIL_CLIENT_SETTINGS } from '@/shared/constants/account-security'
import type { IconName } from '@/shared/constants/icons'
@@ -22,28 +22,28 @@ export interface MailClientGuide {
export const MAIL_CLIENTS_TEXT = {
title: '메일 프로그램 연결',
- description: '아웃룩 · 썬더버드 · 휴대폰 메일 앱에서 회사 메일을 보는 방법입니다.',
+ description: '아웃룩, 썬더버드, 휴대폰 메일 앱에서 회사 메일을 보는 방법입니다.',
settingsTitle: '메일 프로그램 설정값',
/** 설정값 상자의 비밀번호 줄 — 2단계 인증을 켠 직원 (상태를 모르면 이쪽 — 더 안전한 쪽) */
- passwordOn: '앱 비밀번호 — 웹메일 비밀번호가 아닙니다',
+ passwordOn: '웹메일 비밀번호가 아닌 앱 비밀번호',
/** 설정값 상자의 비밀번호 줄 — 켜지 않은 직원 */
- passwordOff: '앱 비밀번호(권장) — 2단계 인증을 켜지 않았으면 웹메일 비밀번호도 됩니다',
+ passwordOff: '앱 비밀번호를 권합니다. 2단계 인증을 켜지 않았다면 웹메일 비밀번호로도 연결됩니다.',
requiredTitle: '비밀번호 칸에는 꼭 앱 비밀번호를 넣어 주세요',
- requiredText: '2단계 인증을 켜 두어 웹메일 비밀번호로는 연결되지 않습니다. 프로그램마다 앱 비밀번호를 하나씩 만들어 넣습니다.',
+ requiredText: '2단계 인증을 켜 두어 웹메일 비밀번호로는 연결되지 않습니다. 프로그램마다 앱 비밀번호를 하나씩 만들어 넣어 주세요.',
recommendTitle: '앱 비밀번호로 연결하기를 권합니다',
recommendText:
'지금은 웹메일 비밀번호로도 연결되지만, 2단계 인증을 켜면 다시 연결할 때 막힙니다. 앱 비밀번호는 기기마다 따로 지울 수 있어 잃어버렸을 때도 안전합니다.',
createAppPassword: '앱 비밀번호 만들기',
- stepsTitle: '프로그램별 연결 차례',
- stepsHint: '쓰는 프로그램을 누르면 차례가 펼쳐집니다.',
+ stepsTitle: '프로그램별 연결 방법',
+ stepsHint: '쓰는 프로그램을 누르면 연결 방법이 펼쳐집니다.',
/** 권하지 않음으로 정했다(2026-10-31) — 회사가 허용하면 이 줄을 지운다 */
cloudWarning:
- '새 Outlook · 휴대폰 Outlook 앱은 메일을 Microsoft 클라우드를 거쳐 받습니다 — 회사 정책에 따라 쓰지 않는 것이 좋습니다.',
+ '새 Outlook과 휴대폰 Outlook 앱은 메일을 Microsoft 클라우드를 거쳐 받습니다. 회사 정책에 따라 쓰지 않는 것이 좋습니다.',
listLabel: '메일 프로그램',
notes: [
- '연결이 안 되면 사용자 이름에 메일 주소 전체를, 비밀번호 칸에 앱 비밀번호(띄어쓰기 없이 40자)를 넣었는지 확인해 주세요.',
- 'POP3 로는 연결할 수 없습니다 — 계정 유형은 IMAP 을 골라 주세요.',
- '웹메일 비밀번호를 바꾸면 앱 비밀번호도 모두 지워집니다 — 프로그램마다 새로 만들어 넣어 주세요.',
+ '연결이 안 되면 사용자 이름에 메일 주소 전체를 넣었는지, 비밀번호 칸에 앱 비밀번호 40자를 띄어쓰기 없이 넣었는지 확인해 주세요.',
+ 'POP3로는 연결할 수 없으니 계정 유형은 IMAP을 골라 주세요.',
+ '웹메일 비밀번호를 바꾸면 앱 비밀번호도 모두 지워지니, 프로그램마다 새로 만들어 넣어 주세요.',
'그래도 안 되면 관리자에게 문의해 주세요.',
],
/** 앱 비밀번호 화면의 설정값 상자 아래 한 줄 — 「메일 프로그램 연결」이 이 화면 링크 */
@@ -52,23 +52,21 @@ export const MAIL_CLIENTS_TEXT = {
shortcutAfter: '에서 볼 수 있습니다.',
} as const
-const PORTS = `${imapPort} · ${smtpPort}`
-
/** 프로그램별 연결 차례 — 아웃룩 · 새 Outlook · 썬더버드 · iPhone · 안드로이드 (맥 메일 · 휴대폰 Outlook 앱은 넣지 않는다) */
export const MAIL_CLIENT_GUIDES: readonly MailClientGuide[] = [
{
id: 'outlook',
name: '아웃룩 (Windows)',
- description: 'Microsoft 365 · Outlook 2016 이후의 데스크톱 Outlook',
+ description: 'Microsoft 365나 Outlook 2016 이후의 데스크톱 Outlook',
icon: 'monitor',
steps: [
- ['Outlook 에서 [파일] → [계정 추가]를 누릅니다.'],
- ['메일 주소를 넣고 [고급 옵션] → 「내 계정을 수동으로 설정」을 고른 뒤 [연결]을 누릅니다.'],
- ['계정 유형에서 「IMAP」을 고릅니다.'],
+ ['Outlook에서 [파일] → [계정 추가]를 누릅니다.'],
+ ['메일 주소를 넣고 [고급 옵션] → [내 계정을 수동으로 설정]을 고른 뒤 [연결]을 누릅니다.'],
+ ['계정 유형에서 [IMAP]을 고릅니다.'],
[
- '받는 메일 · 보내는 메일 모두 서버 ',
+ '받는 메일과 보내는 메일 모두 서버는 ',
{ strong: host },
- `, 포트는 ${PORTS}, 암호화 방법은 「${security}」를 넣고 [다음]을 누릅니다.`,
+ `로 넣습니다. 포트는 받는 메일 ${imapPort}, 보내는 메일 ${smtpPort}이고, 암호화 방법은 [${security}]를 고른 뒤 [다음]을 누릅니다.`,
],
['비밀번호 칸에 앱 비밀번호를 넣고 [연결]을 누릅니다.'],
],
@@ -76,24 +74,24 @@ export const MAIL_CLIENT_GUIDES: readonly MailClientGuide[] = [
{
id: 'new-outlook',
name: '새 Outlook',
- description: 'Windows 의 새 Outlook — 메일이 Microsoft 클라우드를 거칩니다',
+ description: '메일이 Microsoft 클라우드를 거치는 Windows의 새 Outlook',
icon: 'monitor',
steps: [
['[설정](톱니) → [계정] → [메일 계정] → [계정 추가]를 누릅니다.'],
- ['메일 주소를 넣고 [계속] → 공급자에서 「IMAP」을 고릅니다.'],
- ['[고급 설정 표시]를 누르고 받는 · 보내는 서버에 위 설정값을, 비밀번호 칸 둘 다에 앱 비밀번호를 넣습니다.'],
- ['[계속]을 누르면 메일을 Microsoft 클라우드에 동기화한다는 안내가 나옵니다 — 위 주의를 읽고 정합니다.'],
+ ['메일 주소를 넣고 [계속]을 누른 뒤 공급자에서 [IMAP]을 고릅니다.'],
+ ['[고급 설정 표시]를 누르고 받는 서버와 보내는 서버에 위 설정값을, 비밀번호 칸 두 곳에 모두 앱 비밀번호를 넣습니다.'],
+ ['[계속]을 누르면 메일을 Microsoft 클라우드에 동기화한다는 안내가 나옵니다. 위의 주의 글을 읽고 정해 주세요.'],
],
},
{
id: 'thunderbird',
name: '썬더버드',
- description: 'Windows · Mac — 주소와 앱 비밀번호만 넣으면 됩니다',
+ description: 'Windows와 Mac에서 주소와 앱 비밀번호만 넣으면 됩니다',
icon: 'monitor',
steps: [
['썬더버드에서 [≡] → [새로 만들기] → [기존 메일 계정]을 누릅니다.'],
- ['이름 · 메일 주소 · 비밀번호(앱 비밀번호)를 넣고 [계속]을 누릅니다.'],
- ['메일 주소와 앱 비밀번호만 넣으면 설정을 스스로 찾습니다 — 「IMAP」이 보이면 [완료]를 누릅니다.'],
+ ['이름과 메일 주소를 넣고, 비밀번호 칸에 앱 비밀번호를 넣은 뒤 [계속]을 누릅니다.'],
+ ['메일 주소와 앱 비밀번호만 넣으면 설정을 스스로 찾습니다. [IMAP]이 보이면 [완료]를 누릅니다.'],
['찾지 못하면 [수동 설정]에서 위 설정값을 넣습니다.'],
],
},
@@ -103,29 +101,29 @@ export const MAIL_CLIENT_GUIDES: readonly MailClientGuide[] = [
description: '기본 메일 앱',
icon: 'smartphone',
steps: [
- ['[설정] → [메일] → [계정] → [계정 추가] → [기타] → [메일 계정 추가]를 누릅니다. (iOS 18 은 [설정] → [앱] → [메일])'],
- ['이름 · 메일 주소 · 암호(앱 비밀번호) · 설명을 넣고 [다음]을 누릅니다.'],
+ ['[설정] → [메일] → [계정] → [계정 추가] → [기타] → [메일 계정 추가]를 누릅니다. iOS 18에서는 [설정] → [앱] → [메일]에서 시작합니다.'],
+ ['이름, 메일 주소, 설명을 넣고, 암호 칸에 앱 비밀번호를 넣은 뒤 [다음]을 누릅니다.'],
[
- '「IMAP」을 고르고, 받는 · 보내는 메일 서버의 호스트 이름에 ',
+ '[IMAP]을 고르고, 받는 메일 서버와 보내는 메일 서버의 호스트 이름에 ',
{ strong: host },
- ', 사용자 이름에 메일 주소 전체, 암호에 앱 비밀번호를 넣습니다 — 보내는 메일 서버도 「선택 사항」이라 보여도 비우지 않습니다.',
+ ', 사용자 이름에 메일 주소 전체, 암호에 앱 비밀번호를 넣습니다. 보내는 메일 서버 칸이 선택 사항으로 보여도 꼭 채워 주세요.',
],
- [`[다음] → [저장]을 누릅니다. 포트는 스스로 ${PORTS} 로 맞춥니다.`],
+ [`[다음] → [저장]을 누릅니다. 포트는 받는 메일 ${imapPort}, 보내는 메일 ${smtpPort}로 저절로 맞춰집니다.`],
],
},
{
id: 'android',
name: '안드로이드',
- description: 'Gmail 앱 — 다른 계정(IMAP) · 삼성 이메일 앱도 같은 값',
+ description: 'Gmail 앱 기준이며, 삼성 이메일 앱 같은 다른 메일 앱도 같은 값을 넣습니다',
icon: 'smartphone',
steps: [
['Gmail 앱에서 프로필 사진 → [다른 계정 추가] → [기타]를 누릅니다.'],
- ['메일 주소를 넣고 [수동 설정] → 「개인(IMAP)」을 고릅니다.'],
+ ['메일 주소를 넣고 [수동 설정] → [개인(IMAP)]을 고릅니다.'],
['비밀번호에 앱 비밀번호를 넣습니다.'],
[
- '받는 서버 · 보내는 서버에 ',
+ '받는 서버와 보내는 서버는 ',
{ strong: host },
- `, 포트는 ${PORTS}, 보안 유형은 「${security}」를 넣고 [다음]을 누릅니다.`,
+ `로 넣습니다. 포트는 받는 서버 ${imapPort}, 보내는 서버 ${smtpPort}이고, 보안 유형은 [${security}]를 고른 뒤 [다음]을 누릅니다.`,
],
],
},
diff --git a/frontend/src/shared/constants/mail-editor.ts b/frontend/src/shared/constants/mail-editor.ts
index 40ea588..27c6693 100644
--- a/frontend/src/shared/constants/mail-editor.ts
+++ b/frontend/src/shared/constants/mail-editor.ts
@@ -28,7 +28,7 @@ export const MAIL_BODY_LINE_HEIGHT = 1.75
export const MAIL_BODY_HTML_MAX_LENGTH = 500_000
/** 빈 본문 안내 (디자인 원본 ComposeEditor "빈 본문") */
-export const EDITOR_PLACEHOLDER = '내용을 입력하세요'
+export const EDITOR_PLACEHOLDER = '내용을 입력해 주세요'
// ── 글꼴 · 글자 크기 ─────────────────────────────────────
diff --git a/frontend/src/shared/constants/mail-filter.ts b/frontend/src/shared/constants/mail-filter.ts
index e857bee..0305438 100644
--- a/frontend/src/shared/constants/mail-filter.ts
+++ b/frontend/src/shared/constants/mail-filter.ts
@@ -84,39 +84,39 @@ export const MAIL_FILTER_SUMMARY_TEXT = {
export const MAIL_FILTER_TEXT = {
title: '필터',
description:
- '받은 메일을 조건에 따라 메일함으로 옮기거나, 읽음 · 중요로 표시하거나, 동료에게 전달합니다. 새로 오는 메일에 적용하고, 고를 때는 받은편지함에 이미 있는 메일에도 적용합니다.',
+ '받은 메일을 조건에 따라 메일함으로 옮기거나, 읽음이나 중요로 표시하거나, 동료에게 전달합니다. 새로 오는 메일을 정리하고, 저장할 때 고르면 받은편지함에 이미 있는 메일도 함께 정리합니다.',
create: '새 필터',
/**
* 다 만들었을 때 [새 필터] 옆 · 읽기 화면 ⋯ 「이런 메일로 필터 만들기」 아래 ·
* 되돌리기 · 다른 곳에서 지워진 규칙의 저장이 한도를 넘을 때(오류 알림 · 고치기 창 안)
*/
fullNote: `필터는 ${MAIL_FILTER_MAX_RULES}개까지 만들 수 있습니다. 쓰지 않는 필터를 지워 주세요.`,
- listLabel: '필터 — 위에서부터 차례로 적용',
- count: (count: string) => `필터 ${count}개 · ${MAIL_FILTER_MAX_RULES}개까지 만들 수 있습니다. 행을 누르면 고칩니다.`,
+ listLabel: '필터 목록, 위에 있는 필터부터 차례로 정리합니다',
+ count: (count: string) => `필터 ${count}개를 만들었고, ${MAIL_FILTER_MAX_RULES}개까지 만들 수 있습니다. 줄을 누르면 고칠 수 있습니다.`,
// 단추 이름 · 알림의 quoted 는 따옴표로 묶은 이름(quotedRuleTitle — 길면 자르고, 이름에 작은따옴표가 있으면 큰따옴표로 묶는다)
/** 켜기 스위치(role="switch") — 켜짐 · 꺼짐은 aria-checked 가 알린다 (이름은 바꾸지 않는다) */
switchLabel: (quoted: string) => `${quoted} 필터 사용`,
editLabel: (quoted: string) => `${quoted} 필터 고치기`,
moveUp: (quoted: string) => `${quoted} 위로`,
moveDown: (quoted: string) => `${quoted} 아래로`,
- missingMailbox: '옮길 메일함이 지워져 받은편지함으로 들어옵니다 — 눌러서 고치세요.',
+ missingMailbox: '옮길 메일함이 지워져 메일이 받은편지함으로 들어옵니다. 눌러서 고쳐 주세요.',
emptyTitle: '아직 필터가 없습니다',
emptyText:
- '보낸 사람 · 받는 사람 · 제목 · 본문의 글로 메일을 골라 메일함으로 옮기거나, 읽음 · 중요로 표시하거나, 동료에게 전달합니다.',
+ '보낸 사람, 받는 사람, 제목, 본문의 글로 메일을 골라 메일함으로 옮기거나, 읽음이나 중요로 표시하거나, 동료에게 전달합니다.',
// 목록 위 노란 줄 — 다른 곳에서 바뀜(409 MAIL_012 — 글은 에러 코드 사전) · 동작하지 않음(isActive: false)
reload: '다시 불러오기',
- inactive: '지금은 필터와 부재중 자동 응답이 동작하지 않습니다. 메일 서버의 규칙이 다른 곳에서 바뀌었습니다.',
+ inactive: '다른 곳에서 메일 설정이 바뀌어 지금은 필터와 부재중 자동 응답이 되지 않습니다.',
reactivate: '다시 켜기',
// 아래 안내 상자 — 첫 줄의 "위에서부터 차례로" · 둘째 줄의 칸 이름은 굵게 (7단계 — 디자인 원본 MailFilterApply 의 글)
- noteOrderStrong: '위에서부터 차례로',
- noteOrderAfter: ' 적용합니다. 메일함으로 옮기는 필터에 맞으면 그 아래 필터는 적용하지 않습니다 — 순서는 ▲ ▼ 로 바꿉니다.',
- noteApplyBefore: '이미 받은 메일에도 적용하려면 필터를 저장할 때 ',
- noteApplyStrong: '「받은편지함에 이미 있는 메일에도 적용」',
- noteApplyAfter: '을 고릅니다. 받은편지함의 메일만 보고, 그 필터 하나만 적용합니다.',
- noteSpam: '스팸으로 판정되어 들어오는 메일에는 필터를 적용하지 않습니다 — 그대로 스팸메일함으로 갑니다.',
+ noteOrderStrong: '위에 있는 필터부터 차례로',
+ noteOrderAfter: ' 메일을 정리합니다. 메일함으로 옮기는 필터에 맞은 메일은 그 아래 필터를 거치지 않습니다. 순서는 ▲ ▼ 로 바꿀 수 있습니다.',
+ noteApplyBefore: '이미 받은 메일도 정리하려면 필터를 저장할 때 ',
+ noteApplyStrong: '[받은편지함에 이미 있는 메일에도 적용]',
+ noteApplyAfter: '을 골라 주세요. 받은편지함에 있는 메일만 그 필터 하나로 정리합니다.',
+ noteSpam: '스팸으로 분류된 메일은 필터를 거치지 않고 그대로 스팸메일함으로 갑니다.',
noteForward:
- '전달은 사내 주소로만, 새로 오는 메일만 합니다. 메일 한 통은 한 곳으로만 전달됩니다(위의 필터가 먼저). 전달해도 내 메일함에 그대로 남습니다.',
- noteVacation: '부재중 자동 응답은 필터와 함께 동작합니다.',
+ '전달은 새로 오는 메일만 사내 주소로 합니다. 메일 한 통은 한 곳으로만 전달되고, 전달하는 필터가 여럿이면 위에 있는 필터가 먼저입니다. 전달해도 내 메일함에 그대로 남습니다.',
+ noteVacation: '필터가 메일을 옮겨도 부재중 자동 응답은 그대로 보냅니다.',
// 알림 — 켜기 · 끄기 · 순서 바꾸기는 목록이 바로 바뀌므로 알리지 않는다 (실패하면 되돌리고 오류 알림)
saved: '필터를 저장했습니다.',
removed: (quoted: string) => `${quoted} 필터를 지웠습니다.`,
@@ -133,18 +133,18 @@ export const MAIL_FILTER_DIALOG_TEXT = {
nameOptional: '(선택)',
namePlaceholder: '비워 두면 조건이 이름이 됩니다',
matchLegend: '어떤 메일에',
- conditionField: (no: number) => `조건 ${no} — 어디에서`,
- conditionOperator: (no: number) => `조건 ${no} — 포함 · 포함하지 않음`,
- conditionValue: (no: number) => `조건 ${no} — 찾을 글`,
+ conditionField: (no: number) => `조건 ${no}의 찾을 곳`,
+ conditionOperator: (no: number) => `조건 ${no}의 포함 여부`,
+ conditionValue: (no: number) => `조건 ${no}의 찾을 글`,
conditionRemove: (no: number) => `조건 ${no} 지우기`,
addCondition: '조건 더하기',
- conditionHint: `${MAIL_FILTER_MAX_CONDITIONS}개까지 · 글자는 대소문자를 가리지 않습니다`,
+ conditionHint: `조건은 ${MAIL_FILTER_MAX_CONDITIONS}개까지 넣을 수 있고, 영문 대소문자는 가리지 않습니다`,
actionsLegend: '할 일',
move: '메일함으로 옮기기',
moveTarget: '옮길 메일함',
- moveHint: '옮기면 이 아래 필터는 적용하지 않습니다.',
+ moveHint: '옮긴 메일은 이 아래 필터를 거치지 않습니다.',
/** 옮길 메일함이 지워졌다 — 이 항목이 골라진 채로 보인다 (다시 고를 수는 없다) */
- missingMailboxOption: '(지워진 메일함) — 다른 메일함을 고르세요',
+ missingMailboxOption: '지워진 메일함이니 다른 메일함을 골라 주세요',
markAsRead: '읽음으로 표시',
flag: '중요 표시',
forward: '동료에게 전달',
@@ -157,7 +157,7 @@ export const MAIL_FILTER_DIALOG_TEXT = {
cancel: '취소',
// 칸 아래 빨간 글 — [저장]을 누를 때 검사한다(알림은 띄우지 않는다). 그 밖의 형식 오류(이름 · 찾을 글의 길이 · 제어 문자)는 에러 코드 사전의 VAL_001
conditionEmpty: '비어 있는 조건이 있습니다. 찾을 글을 적거나 그 조건을 지워 주세요.',
- conditionVariable: '‘${’ 는 쓸 수 없습니다 — 메일 서버가 다른 뜻으로 읽습니다.',
+ conditionVariable: '‘${’는 필터에 쓸 수 없습니다. 이 글자를 빼고 넣어 주세요.',
// 전달 주소 — 캔버스에는 바깥 주소만 있다. 빈 칸 · 형식 · 내 주소는 2026-10-28 구현하며 정함 (형식 문구는 서버의 거절 문구와 같다)
forwardEmpty: '전달할 주소를 입력해 주세요.',
forwardInvalid: '전달할 주소 형식이 올바르지 않습니다.',
@@ -173,17 +173,17 @@ export const MAIL_FILTER_DIALOG_TEXT = {
*/
export const MAIL_FILTER_APPLY_TEXT = {
label: '받은편지함에 이미 있는 메일에도 적용',
- help: '저장한 뒤 받은편지함에서 조건에 맞는 메일을 찾아 바로 적용합니다.',
+ help: '저장하면 받은편지함에서 조건에 맞는 메일을 찾아 바로 정리합니다.',
/** 전달도 하는 필터 — 설명 뒤에 더한다 */
helpForward: ' 전달은 새로 오는 메일에만 합니다.',
- forwardOnly: '전달만 하는 필터는 이미 받은 메일에 할 일이 없습니다 — 전달은 새로 오는 메일에만 합니다.',
+ forwardOnly: '전달은 새로 오는 메일에만 해서, 전달만 하는 필터는 이미 받은 메일에 할 일이 없습니다.',
/** 옮길 메일함이 지금 메일함 목록에 없다 — 읽음 · 중요 없이 옮기기만 (7단계 리뷰로 더한 경우) */
- missingMailbox: '옮길 메일함이 지워져 이미 받은 메일에 할 일이 없습니다 — 다른 메일함을 고르세요.',
+ missingMailbox: '옮길 메일함이 지워져 이미 받은 메일에 할 일이 없습니다. 다른 메일함을 골라 주세요.',
/** 받은편지함으로 옮기기만 — 이미 받은편지함에 있는 메일이다 (7단계 리뷰로 더한 경우) */
inboxOnly: '받은편지함으로 옮기기만 하는 필터는 이미 받은 메일에 할 일이 없습니다.',
- busy: '다른 필터를 받은편지함에 적용하는 중입니다. 끝난 뒤에 고를 수 있습니다.',
+ busy: '다른 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤에 고를 수 있습니다.',
/** 상자 아래 한 줄 */
- spam: '스팸으로 판정되어 들어오는 메일에는 적용하지 않습니다.',
+ spam: '스팸으로 분류된 메일은 필터를 거치지 않습니다.',
} as const
/**
@@ -191,26 +191,26 @@ export const MAIL_FILTER_APPLY_TEXT = {
* quoted 는 따옴표로 묶은 이름(quotedRuleTitle), 수는 formatCount 로 만든 글자. 확인 = 지금까지 확인한 수, 전체 = 확인 + 남은 수(어림)
*/
export const MAIL_FILTER_RUN_TEXT = {
- progress: (quoted: string) => `${quoted} 필터를 받은편지함에 적용하는 중`,
+ progress: (quoted: string) => `${quoted} 필터로 받은편지함을 정리하는 중`,
/** 진행 문구 뒤 — 전체를 아직 모르면(받은편지함 수를 모르고 첫 응답 전) 뺀다 */
progressCount: (scanned: string, total: string) => ` · ${scanned} / ${total}통 확인`,
- done: (quoted: string, matched: string) => `${quoted} 필터를 받은편지함의 메일 ${matched}통에 적용했습니다.`,
+ done: (quoted: string, matched: string) => `${quoted} 필터로 받은편지함의 메일 ${matched}통을 정리했습니다.`,
/** 맞는 메일이 없었다 */
none: (quoted: string) => `받은편지함에 ${quoted} 필터에 맞는 메일이 없습니다.`,
/** 옮기는 필터의 끝 알림 단추 — 그 메일함으로 */
viewMailbox: (mailbox: string) => `${mailbox} 보기`,
/** 도중에 멈췄다 — [다시 시도]는 남은 메일부터 이어서 */
stopped: (quoted: string, total: string, scanned: string, matched: string) =>
- `${quoted} 필터 — ${total}통 가운데 ${scanned}통까지 확인하고 멈췄습니다(${matched}통에 적용). 다시 시도해 주세요.`,
+ `${quoted} 필터로 ${total}통 가운데 ${scanned}통까지 확인하고 멈췄습니다. 그동안 ${matched}통을 정리했습니다. 다시 시도해 주세요.`,
/** 처음 요청부터 실패했다 — 필터는 저장되어 있다(새로 오는 메일에는 적용된다) */
- notApplied: '필터는 저장했지만 받은편지함에 적용하지 못했습니다. 다시 시도해 주세요.',
+ notApplied: '필터는 저장했지만 받은편지함은 정리하지 못했습니다. 다시 시도해 주세요.',
/** 429 REQ_001 — 같은 직원의 적용이 다른 창 · PC 에서 진행 중 */
- otherWindow: '다른 창에서 필터를 받은편지함에 적용하는 중입니다. 끝난 뒤 다시 시도해 주세요.',
+ otherWindow: '다른 창에서 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요.',
/**
* 이 탭에서 다른 필터를 적용하는 중에 [다시 시도]를 눌렀다 (보드에 없던 경우 — 2026-10-29 구현하며 정함).
* 같은 [다시 시도](같은 자리 · 같은 수)를 붙여 앞 적용이 끝난 뒤 다시 누를 수 있게 한다
*/
- busy: '다른 필터를 받은편지함에 적용하는 중입니다. 끝난 뒤 다시 시도해 주세요.',
+ busy: '다른 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요.',
retry: '다시 시도',
} as const
diff --git a/frontend/src/shared/constants/mail-notify.ts b/frontend/src/shared/constants/mail-notify.ts
index 7ac3ad8..1b86cc3 100644
--- a/frontend/src/shared/constants/mail-notify.ts
+++ b/frontend/src/shared/constants/mail-notify.ts
@@ -53,26 +53,26 @@ export const MAIL_NOTIFY_TEXT = {
menu: '알림',
title: '알림',
description:
- '새 메일은 1분을 기다리지 않고 바로 목록에 나타납니다. 다른 창을 보는 동안에도 알고 싶으면 바탕화면 알림을 켜 두세요.',
+ '새 메일은 1분을 기다리지 않고 바로 목록에 나타납니다. 다른 창을 보는 동안에도 알고 싶으면 바탕화면 알림을 켜 주세요.',
enable: '새 메일이 오면 바탕화면 알림 보내기',
- noteAsk: '켜면 브라우저가 알림을 허락할지 한 번 묻습니다 — 「허용」을 눌러 주세요.',
+ noteAsk: '켜면 브라우저가 알림을 허락할지 한 번 묻습니다. [허용]을 눌러 주세요.',
noteGranted: '이 브라우저에서 알림이 허용되어 있습니다.',
noteBlocked:
- '브라우저가 이 사이트의 알림을 막고 있습니다. 주소창 왼쪽의 사이트 정보(자물쇠) › 알림을 「허용」으로 바꾼 뒤 이 화면을 새로 고쳐 주세요.',
+ '브라우저가 이 사이트의 알림을 막고 있습니다. 주소창 왼쪽의 사이트 정보(자물쇠)를 눌러 [알림]을 [허용]으로 바꾼 뒤 이 화면을 새로 고쳐 주세요.',
noteUnsupported: '이 브라우저는 바탕화면 알림을 지원하지 않습니다. 새 메일은 그래도 바로 목록에 나타납니다.',
contentLegend: '알림에 보일 것',
showSubject: '메일 제목도 보이기',
- showSubjectHint: '끄면 보낸 사람 이름만 보입니다 — 화면을 다른 사람과 함께 보는 자리라면 꺼 두세요.',
+ showSubjectHint: '끄면 보낸 사람 이름만 보입니다. 다른 사람과 화면을 함께 보는 자리라면 꺼 두는 것이 좋습니다.',
test: '시험 알림 보내기',
- testHint: '바탕화면 오른쪽 아래(맥은 오른쪽 위)에 알림이 뜨면 됩니다.',
+ testHint: '바탕화면 오른쪽 아래에 알림이 뜨면 됩니다. 맥에서는 오른쪽 위에 뜹니다.',
realtimeTitle: '실시간 연결',
- realtimeLive: '연결되어 있습니다 — 새 메일이 오면 바로 목록에 나타납니다.',
+ realtimeLive: '연결되어 있어 새 메일이 오면 바로 목록에 나타납니다.',
realtimeLiveHint:
- '연결할 수 없을 때는 "연결할 수 없어 1분마다 확인합니다."로 바뀌고, 저절로 다시 잇습니다. 회사 방화벽 · 프록시가 오래 열린 연결을 끊으면 이렇게 됩니다.',
+ '연결이 끊기면 1분마다 새 메일을 확인하면서 저절로 다시 연결합니다. 회사 방화벽이나 프록시가 오래 열린 연결을 끊으면 이렇게 될 수 있습니다.',
realtimeDown: '연결할 수 없어 1분마다 확인합니다.',
/** 끊긴 동안 — 상태 줄 바로 아래 (2026-10-28 화면 점검 반영) */
realtimeDownNotice: '연결될 때까지 바탕화면 알림은 오지 않습니다.',
- realtimeDownHint: '저절로 다시 잇습니다. 회사 방화벽 · 프록시가 오래 열린 연결을 끊으면 이렇게 됩니다.',
+ realtimeDownHint: '저절로 다시 연결합니다. 회사 방화벽이나 프록시가 오래 열린 연결을 끊으면 이렇게 될 수 있습니다.',
/** 처음 잇는 동안 (캔버스에는 없다 — 2026-10-28 구현하며 정함) */
realtimeConnecting: '연결하는 중입니다…',
/** Web Locks · BroadcastChannel 이 없는 브라우저 — 1분 확인만 한다 (캔버스에는 없다 — 2026-10-28 구현하며 정함) */
@@ -80,9 +80,9 @@ export const MAIL_NOTIFY_TEXT = {
/** 아래 안내 상자 — 첫 줄의 "이 브라우저에만"은 굵게 */
noteBrowserBefore: '알림 설정은 ',
noteBrowserStrong: '이 브라우저에만',
- noteBrowserAfter: ' 적용됩니다. 다른 PC · 브라우저에서는 그곳에서 따로 켜 주세요.',
+ noteBrowserAfter: ' 저장됩니다. 다른 PC나 브라우저에서는 그곳에서 따로 켜 주세요.',
noteOpenTab: '웹메일 탭이 하나라도 열려 있어야 알림이 옵니다. 탭을 모두 닫으면 오지 않습니다.',
- noteVisible: '웹메일 화면을 보고 있을 때는 바탕화면 알림을 보내지 않습니다 — 목록에 바로 보이므로.',
+ noteVisible: '웹메일 화면을 보고 있을 때는 새 메일이 목록에 바로 보이므로 바탕화면 알림을 보내지 않습니다.',
// 시험 알림 — 지금 탭에서 바로 보낸다
testTitle: 'ARA 웹메일',
@@ -106,6 +106,6 @@ export const MAIL_NOTIFY_TEXT = {
/** 허락을 받아 켰을 때 (성공 알림) */
enabledToast: '바탕화면 알림을 켰습니다.',
/** 브라우저가 막았을 때 (오류 알림 + 설정 열기) */
- blockedToast: '브라우저가 알림을 막고 있습니다. 푸는 방법은 설정 › 알림에 있습니다.',
+ blockedToast: '브라우저가 알림을 막고 있습니다. 푸는 방법은 [설정] → [알림]에서 볼 수 있습니다.',
openSettings: '설정 열기',
} as const
diff --git a/frontend/src/shared/constants/mail-send-later.ts b/frontend/src/shared/constants/mail-send-later.ts
index e655d68..5a0e956 100644
--- a/frontend/src/shared/constants/mail-send-later.ts
+++ b/frontend/src/shared/constants/mail-send-later.ts
@@ -46,10 +46,10 @@ export const SEND_LATER_TEXT = {
customDate: '날짜',
customTime: '시각',
/** zone 은 브라우저가 알려 주는 시간대 이름(Intl — "한국 표준시" 등) */
- note: (zone: string) => `이 PC 의 시간(${zone})으로 보냅니다 · 7일 안에서 고를 수 있습니다.`,
+ note: (zone: string) => `이 PC의 시간(${zone})으로 보내고, 7일 안에서 고를 수 있습니다.`,
/** 시간대 이름을 모를 때 */
- noteWithoutZone: '이 PC 의 시간으로 보냅니다 · 7일 안에서 고를 수 있습니다.',
- tooSoon: '지금부터 5분 뒤 이후의 시각을 고르세요.',
+ noteWithoutZone: '이 PC의 시간으로 보내고, 7일 안에서 고를 수 있습니다.',
+ tooSoon: '5분 뒤보다 이른 시각은 고를 수 없습니다. 더 늦은 시각을 골라 주세요.',
tooLate: '7일 안의 시각만 고를 수 있습니다.',
confirm: '예약',
cancel: '취소',
@@ -68,10 +68,10 @@ export const SEND_LATER_TEXT = {
/** 메일함 패널의 「예약됨」 · 목록 머리 */
mailbox: '예약됨',
/** 목록 머리의 개수 — "2통 · 보낼 시각 차례" */
- listCount: (count: string) => `${count}통 · 보낼 시각 차례`,
+ listCount: (count: string) => `${count}통 · 보낼 시각이 이른 순서`,
/** 목록이 비었을 때 (보드에 없다 — 보낸 · 취소한 뒤 그 자리에서 비면) */
emptyTitle: '예약한 메일이 없습니다',
- emptyHint: '쓰기 화면의 [보내기 ▾] › 예약 발송…으로 보낼 시각을 정할 수 있습니다.',
+ emptyHint: '메일을 쓸 때 [보내기 ▾] → [예약 발송…]으로 보낼 시각을 정할 수 있습니다.',
/** 표 · 목록의 보낼 시각 칸 이름 */
sendAtColumn: '보낼 시각',
/** 읽기 화면 머리 아래 청록 줄 — "10월 30일 (금) 오전 9:00 에 보낼 메일입니다." */
@@ -97,7 +97,7 @@ export const SEND_LATER_TEXT = {
/** 보내기 취소를 기다리는 동안 */
rowSending: '보내는 중',
/** 아직 보내지 않은 메일의 고르기 칸(꺼짐)의 까닭 — 옮기거나 지워도 엔진은 그 시각에 보낸다 (보드에 없다) */
- heldNotSelectable: '보내기를 기다리는 메일은 고를 수 없습니다 — 열어서 취소하거나 삭제해 주세요.',
+ heldNotSelectable: '보내기를 기다리는 메일은 고를 수 없습니다. 메일을 열어 취소하거나 삭제해 주세요.',
} as const
/** 보낼 시각 뒤 이만큼 기다렸다 보이는 목록 · 열린 메일 · 예약됨 수를 다시 본다 — 엔진이 풀어도 새 메일 확인 · 실시간 알림은 바뀌지 않는다 */
diff --git a/frontend/src/shared/constants/mail-settings.ts b/frontend/src/shared/constants/mail-settings.ts
index 88b4647..14942eb 100644
--- a/frontend/src/shared/constants/mail-settings.ts
+++ b/frontend/src/shared/constants/mail-settings.ts
@@ -11,24 +11,24 @@ export const EXTERNAL_IMAGES_OPTIONS: readonly ExternalImagesSetting[] = ['ask',
export const MAIL_SETTINGS_TEXT = {
title: '보내기 · 읽기',
/** 보드에 없다 — 저장 단추가 없으니 고르는 대로 저장된다는 것을 머리에서 알린다 */
- description: '고르는 대로 저장합니다. 이 설정은 어느 PC 에서나 같습니다.',
+ description: '고르는 대로 바로 저장됩니다. 이 설정은 어느 PC에서나 같습니다.',
saved: '저장했습니다',
undoTitle: '보내기 취소',
undoOff: '끔',
undoSeconds: (seconds: number) => `${seconds}초`,
undoHint:
- '[보내기]를 누른 뒤 이 시간 동안 [보내기 취소]로 되돌릴 수 있습니다. 그동안 메일은 메일 서버에서 기다리므로 창을 닫아도 보내집니다.',
+ '[보내기]를 누른 뒤 이 시간 동안 [보내기 취소]로 되돌릴 수 있습니다. 그사이 창을 닫아도 시간이 지나면 메일은 그대로 보내집니다.',
imagesTitle: '외부 이미지',
images: {
ask: { label: '늘 묻기', hint: '메일마다 [이미지 보기]를 눌러 봅니다' },
trusted: {
- label: '주소록 · 사내 직원의 메일은 늘 보기',
+ label: '주소록과 사내 직원의 메일은 늘 보기',
hint: '보낸 사람이 내 주소록이나 사내 목록에 있으면 바로 보입니다',
},
always: { label: '늘 보기', hint: '스팸메일함의 메일은 이 설정과 상관없이 묻습니다' },
} satisfies Record,
imagesNote:
- '외부 이미지는 사내 메일 서버가 대신 받아 보여 줍니다 — 보낸 사람에게 이 PC 의 주소는 드러나지 않습니다. 다만 이미지를 받으면 보낸 사람은 메일을 열어 본 것을 알 수 있습니다.',
+ '외부 이미지는 회사를 거쳐 불러오므로 보낸 사람은 이 PC의 주소를 알 수 없습니다. 다만 이미지를 불러오면 보낸 사람은 메일을 열어 본 것을 알 수 있습니다.',
} as const
diff --git a/frontend/src/shared/constants/mail-signature.ts b/frontend/src/shared/constants/mail-signature.ts
index eae99c4..2f8b4a9 100644
--- a/frontend/src/shared/constants/mail-signature.ts
+++ b/frontend/src/shared/constants/mail-signature.ts
@@ -59,15 +59,15 @@ export const SIGNATURE_TEXT = {
'보내는 메일 끝에 넣는 글입니다. 쓰기 화면을 열 때 본문에 넣어 두므로, 그 메일에서만 고치거나 지울 수도 있습니다.',
fieldLabel: '서명 내용',
hint:
- '굵게 · 기울임 · 밑줄 · 글자 색 · 링크 · 그림을 쓸 수 있습니다. 그림은 PNG · JPG · GIF, 한 장 300KB 이하, 3장까지이고 ' +
- '붙여 넣거나 끌어다 놓아도 됩니다. 메일 안에 함께 실어 보내므로 받는 쪽에서 외부 이미지처럼 막히지 않습니다.',
+ '굵게, 기울임, 밑줄, 글자 색, 링크, 그림을 쓸 수 있습니다. 그림은 PNG, JPG, GIF 파일을 한 장 300KB 이하로 3장까지 넣을 수 있고 ' +
+ '붙여 넣거나 끌어다 놓아도 됩니다. 그림은 메일에 함께 실어 보내므로 받는 쪽에서 외부 이미지처럼 막히지 않습니다.',
usageLegend: '서명을 넣을 메일',
forNew: '새 메일',
- forReply: '답장 · 전달',
- forReplyNote: '— 인용한 원문 위에 넣습니다',
+ forReply: '답장과 전달',
+ forReplyNote: '인용한 원문 위에 넣습니다',
save: '저장',
saving: '저장하는 중…',
- saved: '저장했습니다. 새로 여는 쓰기 화면부터 들어갑니다.',
+ saved: '저장했습니다. 다음에 메일을 쓸 때부터 들어갑니다.',
uploading: '그림을 올리는 중…',
imageSizeLabel: '그림 크기',
imageSizes: { small: '작게', medium: '보통', original: '원본' },
@@ -83,8 +83,8 @@ export const SIGNATURE_TEXT = {
export const SIGNATURE_PREVIEW_NOTES = {
newOn: '새 메일은 본문 끝에 서명을 넣습니다.',
newOff: '새 메일에는 서명을 넣지 않습니다.',
- replyOn: '답장 · 전달은 인용한 원문 위에 서명을 넣습니다.',
- replyOff: '답장 · 전달에는 서명을 넣지 않습니다.',
+ replyOn: '답장과 전달에는 인용한 원문 위에 서명을 넣습니다.',
+ replyOff: '답장과 전달에는 서명을 넣지 않습니다.',
} as const
/**
diff --git a/frontend/src/shared/constants/mail-vacation.ts b/frontend/src/shared/constants/mail-vacation.ts
index 42989d5..8f3d54e 100644
--- a/frontend/src/shared/constants/mail-vacation.ts
+++ b/frontend/src/shared/constants/mail-vacation.ts
@@ -17,26 +17,26 @@ export const VACATION_NOTICE_REFRESH_MS = 60_000
export const VACATION_TEXT = {
title: '부재중 자동 응답',
- description: '자리를 비운 동안 받은 메일에 정해 둔 글로 자동으로 답합니다. 켜 두면 메일 화면의 메일함 패널 아래에 알립니다.',
+ description: '자리를 비운 동안 받은 메일에 정해 둔 글로 자동으로 답합니다. 켜 둔 동안에는 메일 화면 왼쪽의 메일함 목록 아래에 안내가 보입니다.',
enable: '부재중 자동 응답 사용',
periodLegend: '기간',
untilOff: '끌 때까지 계속',
range: '기간 정하기',
from: '시작',
to: '끝',
- rangeHint: '시작일 0시부터 끝나는 날 자정까지 답합니다 (이 브라우저의 시간대 기준).',
+ rangeHint: '시작일 0시부터 끝나는 날 자정까지 답합니다. 시각은 이 브라우저의 시간대를 따릅니다.',
subject: '제목',
body: '내용',
- bodyHint: '글자만 보냅니다 (서식 · 그림 없음). 서명은 붙지 않습니다.',
+ bodyHint: '서식과 그림 없이 글자만 보내고, 서명은 붙지 않습니다.',
save: '저장',
saving: '저장하는 중…',
previewTitle: '보낸 사람이 받는 답장',
previewFrom: '보낸 사람',
previewSubject: '제목',
- previewNoteOn: '받은 메일의 보낸 사람에게 위 글로 답합니다. 같은 사람에게 매번 보내지는 않습니다(메일 엔진 규칙).',
- previewNoteOff: '꺼져 있습니다 — 자동으로 답하지 않습니다.',
+ previewNoteOn: '받은 메일의 보낸 사람에게 위 글로 답합니다. 같은 사람에게는 매번 보내지 않습니다.',
+ previewNoteOff: '꺼져 있어 자동으로 답하지 않습니다.',
// 저장한 뒤의 상태 글 — 날짜는 "10월 30일(금)" (올해가 아니면 앞에 연도)
- savedOff: '저장했습니다. 자동 응답을 껐습니다 — 써 둔 글은 남겨 둡니다.',
+ savedOff: '저장했습니다. 자동 응답을 껐고, 써 둔 글은 그대로 남아 있습니다.',
savedUntilOff: '저장했습니다. 끌 때까지 자동으로 답합니다.',
savedRange: (from: string, to: string) => `저장했습니다. ${from}부터 ${to}까지 자동으로 답합니다.`,
savedFrom: (from: string) => `저장했습니다. ${from}부터 자동으로 답합니다.`,
@@ -52,7 +52,7 @@ export const VACATION_TEXT = {
* 머리 아래 노란 줄 (7단계 — 디자인 원본 MailFilterApplyStates) — 저장한 값이 켜짐인데 엔진에서 우리 규칙이 꺼져 있을 때(isActive: false).
* 단추는 없다 — 저장 단추는 늘 누를 수 있다. 저장하면 필터도 함께 다시 켜진다
*/
- inactive: '켜 둔 자동 응답이 지금은 동작하지 않습니다. 메일 서버의 규칙이 다른 곳에서 바뀌었습니다 — 아래 [저장]을 누르면 다시 켜집니다.',
+ inactive: '다른 곳에서 메일 설정이 바뀌어 켜 둔 자동 응답이 지금은 나가지 않습니다. 아래 [저장]을 누르면 다시 켜집니다.',
} as const
/** 바꾸고 저장하지 않은 채 떠날 때 (앱 안 이동은 대화상자, 탭 닫기 · 새로고침은 브라우저 확인창) — 서명 설정과 같은 모양 */
diff --git a/frontend/src/shared/constants/mail.ts b/frontend/src/shared/constants/mail.ts
index 9deaf1e..4ff5264 100644
--- a/frontend/src/shared/constants/mail.ts
+++ b/frontend/src/shared/constants/mail.ts
@@ -156,18 +156,18 @@ export const MAIL_SENT_ACTION_LABEL = '보낸편지함 보기'
/**
* 받는 사람 일부가 받지 못한 채 보냈을 때 — "보냈습니다" 대신 오류 알림 모양(흰 카드) + [보낸편지함 보기]
* (계약 2절 "받는 사람별 결과" · 디자인 README "발송 · 부재중 결함 수정" — 캔버스에는 없다).
- * 문장은 shared/utils/mail-send-result.ts 가 짓는다 — "보냈지만 3명은 받지 못했습니다 — a@… (없는 주소), b@… (받는 쪽이 거절) 외 1명"
+ * 문장은 shared/utils/mail-send-result.ts 가 짓는다 — "보냈지만 3명은 받지 못했습니다. a@… (없는 주소), b@… (받는 쪽이 거절) 외 1명"
*/
export const MAIL_PARTIAL_SEND_TEXT = {
/** count 는 formatCount 로 만든 글자, recipients 는 아래 recipient 를 separator 로 이은 것 (+ others) */
- message: (count: string, recipients: string) => `보냈지만 ${count}명은 받지 못했습니다 — ${recipients}`,
+ message: (count: string, recipients: string) => `보냈지만 ${count}명은 받지 못했습니다. ${recipients}`,
/**
* 엔진이 붙잡아 둔 발송(보내기 취소를 기다리는 중 · 9단계)이면 아직 나가지 않았다 — "받지 못합니다" + [보내기 취소]
* (디자인 원본 MailSendLater: "보냈지만 1명은 받지 못합니다 — 주소 (까닭)")
*/
- pendingMessage: (count: string, recipients: string) => `보냈지만 ${count}명은 받지 못합니다 — ${recipients}`,
+ pendingMessage: (count: string, recipients: string) => `보냈지만 ${count}명은 받지 못합니다. ${recipients}`,
/** 예약 발송이면 — "예약했지만 N명은 받지 못합니다 — …" + [예약됨 보기] (보드에 없다) */
- scheduledMessage: (count: string, recipients: string) => `예약했지만 ${count}명은 받지 못합니다 — ${recipients}`,
+ scheduledMessage: (count: string, recipients: string) => `예약했지만 ${count}명은 받지 못합니다. ${recipients}`,
/** 주소와 까닭 사이를 띄운다 — 긴 주소 뒤에서 줄을 바꿔 까닭 낱말이 쪼개지지 않게 */
recipient: (email: string, reason: string) => `${email} (${reason})`,
separator: ', ',
@@ -266,17 +266,17 @@ export const MAIL_SEARCH_TEXT = {
clearTitle: '검색어 지우기 (Esc)',
close: '검색 닫기',
/** "검색 닫기 — 받은편지함으로" — 돌아갈 메일함 이름과 조사는 부르는 쪽이 붙인다 */
- closeTitle: (place: string) => `검색 닫기 — ${place}`,
+ closeTitle: (place: string) => `검색을 닫고 ${place} 돌아가기`,
title: '검색 결과',
quoted: (query: string) => `‘${query}’`,
count: (query: string, count: string) => `‘${query}’ · ${count}통`,
searching: (query: string) => `‘${query}’ · 찾는 중`,
scopeLabel: '찾을 곳',
scopeAll: '전체 메일함',
- scopeAllHint: '휴지통 · 스팸메일함은 빼고 찾습니다',
+ scopeAllHint: '휴지통과 스팸메일함은 빼고 찾습니다',
emptyTitle: (query: string) => `‘${query}’에 맞는 메일이 없습니다`,
emptyHint:
- '낱말 단위로 찾습니다. 붙여 쓴 말의 일부(‘서버이전’의 ‘서버’)나 제목 맨 앞의 말머리([공지] · Re:)로는 찾지 못합니다.',
+ '낱말 단위로 찾습니다. ‘서버이전’의 ‘서버’처럼 붙여 쓴 말의 일부나, 제목 맨 앞의 [공지]나 Re: 같은 말머리로는 찾지 못합니다.',
searchAll: '전체 메일함에서 찾기',
/** 결과 목록의 보이지 않는 이름 (불러오는 동안) */
loading: '찾는 중',
@@ -327,7 +327,7 @@ export const MAIL_SPAM_TEXT = {
/** 스팸메일함 — 받은편지함으로 옮기고 표시를 "스팸 아님"으로 바꾼다 */
notSpam: '스팸 아님',
/** place 에는 메일함 이름과 조사를 붙여 넘긴다 ("받은편지함으로") */
- notSpamMoved: (count: string, place: string) => `메일 ${count}통을 ${place} 옮겼습니다 (스팸 아님).`,
+ notSpamMoved: (count: string, place: string) => `메일 ${count}통을 스팸이 아닌 메일로 표시해 ${place} 옮겼습니다.`,
} as const
/** 읽기 화면 도구 줄의 ⋯ (더 보기 40×44) 메뉴 */
diff --git a/frontend/src/shared/constants/mailbox-manage.ts b/frontend/src/shared/constants/mailbox-manage.ts
index 47ec34e..c9f9f5d 100644
--- a/frontend/src/shared/constants/mailbox-manage.ts
+++ b/frontend/src/shared/constants/mailbox-manage.ts
@@ -38,7 +38,7 @@ export const MAILBOX_DIALOG_TEXT = {
createChildTitle: (parent: string) => `‘${parent}’ 안에 새 메일함`,
renameTitle: '메일함 이름 바꾸기',
nameLabel: '이름',
- nameHelp: '64자까지. 같은 자리에 같은 이름은 쓸 수 없습니다.',
+ nameHelp: '64자까지 쓸 수 있고, 같은 자리에 같은 이름은 쓸 수 없습니다.',
create: '만들기',
rename: '바꾸기',
cancel: '취소',
@@ -72,7 +72,7 @@ export const MAILBOX_OPERATION_TEXT = {
/** 처리할 것이 없었다 — 다른 탭 · 메일 프로그램이 먼저 했다 */
readNone: (mailbox: string) => `${mailbox}에 안 읽은 메일이 없습니다.`,
emptyProgress: (object: string) => `${object} 비우는 중`,
- emptyDone: (object: string, count: string) => `${object} 비웠습니다 — 메일 ${count}통.`,
+ emptyDone: (object: string, count: string) => `메일 ${count}통을 지워 ${object} 비웠습니다.`,
/** subject 에는 메일함 이름에 '이/가'까지 붙여 넘긴다 ("휴지통이") */
emptyNone: (subject: string) => `${subject} 이미 비어 있습니다.`,
deleteProgress: (mailbox: string) => `‘${mailbox}’ 메일함을 지우는 중`,
@@ -81,5 +81,5 @@ export const MAILBOX_OPERATION_TEXT = {
deleteDoneMoved: (mailbox: string, count: string) =>
`‘${mailbox}’ 메일함을 지웠습니다. 안의 메일 ${count}통은 휴지통에 있습니다.`,
/** 도중에 실패 — 거기까지 처리한 수. 다시 하면 남은 것만 처리한다 */
- failed: (done: string, total: string) => `${total}통 가운데 ${done}통까지 처리했습니다 — 다시 시도해 주세요.`,
+ failed: (done: string, total: string) => `${total}통 가운데 ${done}통까지 처리하고 멈췄습니다. 다시 시도해 주세요.`,
} as const
diff --git a/frontend/src/shared/constants/session-restriction.ts b/frontend/src/shared/constants/session-restriction.ts
index af10477..17325ec 100644
--- a/frontend/src/shared/constants/session-restriction.ts
+++ b/frontend/src/shared/constants/session-restriction.ts
@@ -27,34 +27,34 @@ export const LOGIN_PASSWORD_CHANGE_TEXT = {
next: '새 비밀번호',
showNext: '새 비밀번호 보기',
/** 규칙(8자 이상 · 임시 비밀번호와 다름)에 맞지 않으면 이 글이 빨간 오류가 된다 */
- nextHint: '8자 이상 · 임시 비밀번호와 달라야 합니다. 영문 · 숫자 · 기호를 섞으면 더 안전합니다.',
+ nextHint: '8자 이상이고 임시 비밀번호와 달라야 합니다. 영문, 숫자, 기호를 섞으면 더 안전합니다.',
confirm: '새 비밀번호 확인',
mismatch: '새 비밀번호가 서로 다릅니다.',
/** 2단계 인증을 켠 직원만 — 맨 아래 (로그인 때 쓴 코드는 2분 동안 다시 받지 않는다) */
code: '인증 앱 코드',
- codeHint: '로그인할 때 넣은 코드는 다시 쓸 수 없습니다 — 앱에 다음 코드가 뜨면 넣어 주세요.',
+ codeHint: '로그인할 때 넣은 코드는 다시 쓸 수 없으니, 앱에 다음 코드가 뜨면 넣어 주세요.',
submit: '비밀번호 바꾸고 계속',
submitting: '바꾸는 중…',
- note: '메일 프로그램(아웃룩 등)을 연결해 두었다면 바꾼 뒤 다시 연결해야 합니다.',
+ note: '아웃룩 같은 메일 프로그램을 연결해 두었다면 비밀번호를 바꾼 뒤 다시 연결해 주세요.',
/** 끝난 뒤 알림 */
done: '새 비밀번호를 정했습니다.',
} as const
/** 로그인 — 2단계 인증 켜기(필수) (디자인 원본 LoginTwoFactorSetup). QR · 키 · [복사]의 글은 12단계 TWO_FACTOR_ENABLE_TEXT */
export const LOGIN_TWO_FACTOR_SETUP_TEXT = {
- title: '2단계 인증을 켜야 합니다',
+ title: '2단계 인증을 켜 주세요',
subtitle: '회사 정책에 따라 로그인할 때 휴대폰 인증 앱의 코드도 확인합니다. 지금 켜면 바로 메일로 이어집니다.',
scanTitle: '인증 앱으로 QR 코드를 찍어 주세요',
scanHint:
- '인증 앱에서 [+] → 「QR 코드 스캔」을 고릅니다. 앱이 없으면 Google Authenticator · Microsoft Authenticator 를 먼저 설치해 주세요.',
+ '인증 앱에서 [+] → [QR 코드 스캔]을 고릅니다. 앱이 없으면 Google Authenticator나 Microsoft Authenticator를 먼저 설치해 주세요.',
codeLabel: '앱에 표시된 6자리 코드',
/** 화면 메모리에 지금 비밀번호가 없을 때만 ③ */
passwordLabel: '지금 비밀번호',
showPassword: '지금 비밀번호 보기',
- passwordHint: '새로고침해서 다시 넣어야 합니다.',
+ passwordHint: '새로고침한 뒤에는 지금 비밀번호를 한 번 더 넣어 주세요.',
submit: '켜고 계속',
submitting: '켜는 중…',
- note: 'Outlook · 휴대폰 메일 앱은 이 코드를 묻지 않습니다. 켠 뒤에는 설정 › 앱 비밀번호에서 만든 비밀번호로 연결합니다.',
+ note: 'Outlook이나 휴대폰 메일 앱은 이 코드를 묻지 않습니다. 켠 뒤에는 [설정] → [앱 비밀번호]에서 만든 비밀번호로 연결해 주세요.',
/** 켠 뒤 알림 — [앱 비밀번호 만들기](단추가 있는 알림 10초) */
done: '2단계 인증을 켰습니다. 메일 프로그램은 앱 비밀번호로 연결합니다.',
doneAction: '앱 비밀번호 만들기',
@@ -78,12 +78,12 @@ export const TWO_FACTOR_GRACE_TEXT = {
region: '2단계 인증 안내',
/** "11월 30일까지 2단계 인증을 켜 주세요" (굵게) — 날짜는 시작일 전날, 올해가 아니면 해를 붙인다 */
deadline: (day: string) => `${day}까지 2단계 인증을 켜 주세요`,
- barAfter: ' — 그 뒤에는 켜야 로그인할 수 있습니다.',
+ barAfter: '. 그 뒤에는 켜야 로그인할 수 있습니다.',
action: '지금 켜기',
/** 설정 › 2단계 인증 머리 아래 갈색 상자의 둘째 줄 */
settingsText: '회사 정책에 따라 그 뒤에는 2단계 인증을 켜야 로그인할 수 있습니다.',
/** 필수인 직원이 켠 뒤 — [2단계 인증 끄기] 대신 */
cannotDisable: '회사 정책에 따라 끌 수 없습니다',
/** 필수인 직원이 켠 뒤 — 아래 안내의 첫 줄 (12단계 「끈 뒤 새 휴대폰으로 다시 켜 주세요」 대신) */
- noteChangePhone: '휴대폰을 바꿀 때는 관리자에게 2단계 인증 해제를 요청해 주세요 — 다음 로그인 때 새 휴대폰으로 다시 켭니다.',
+ noteChangePhone: '휴대폰을 바꿀 때는 관리자에게 2단계 인증을 풀어 달라고 요청해 주세요. 다음에 로그인할 때 새 휴대폰으로 다시 켜면 됩니다.',
} as const
diff --git a/frontend/src/shared/utils/account-security.spec.ts b/frontend/src/shared/utils/account-security.spec.ts
index aa3b3d4..d235f3e 100644
--- a/frontend/src/shared/utils/account-security.spec.ts
+++ b/frontend/src/shared/utils/account-security.spec.ts
@@ -112,7 +112,7 @@ describe('2단계 인증 상태 글', () => {
it('꺼짐 · 켜짐 — 웹메일에서 켠 때가 있으면 "2026년 10월 31일부터"', () => {
expect(twoFactorStateLabel({ isEnabled: false, enabledAt: null })).toBe('꺼짐')
expect(twoFactorStateLabel({ isEnabled: true, enabledAt: null })).toBe('켜짐')
- expect(twoFactorStateLabel({ isEnabled: true, enabledAt: '2026-10-31T03:00:00Z' })).toBe('켜짐 — 2026년 10월 31일부터')
+ expect(twoFactorStateLabel({ isEnabled: true, enabledAt: '2026-10-31T03:00:00Z' })).toBe('2026년 10월 31일부터 켜짐')
})
})
@@ -167,17 +167,17 @@ describe('로그인 기록', () => {
it('결과 글 다섯 가지 — 실패는 isFailure', () => {
expect(loginResultLabel(event({}))).toEqual({ label: '성공', isFailure: false })
- expect(loginResultLabel(event({ isTwoFactor: true }))).toEqual({ label: '성공 · 2단계 인증', isFailure: false })
+ expect(loginResultLabel(event({ isTwoFactor: true }))).toEqual({ label: '2단계 인증으로 성공', isFailure: false })
expect(loginResultLabel(event({ isSuccess: false, failureReason: 'wrongPassword' }))).toEqual({
- label: '실패 — 비밀번호 틀림',
+ label: '비밀번호가 틀려 실패',
isFailure: true,
})
expect(loginResultLabel(event({ isSuccess: false, failureReason: 'wrongCode' }))).toEqual({
- label: '실패 — 코드 틀림',
+ label: '코드가 틀려 실패',
isFailure: true,
})
expect(loginResultLabel(event({ isSuccess: false, failureReason: 'appPasswordRequired' }))).toEqual({
- label: '실패 — 앱 비밀번호 필요',
+ label: '앱 비밀번호가 필요해 실패',
isFailure: true,
})
// 모르는 까닭 — 「실패」만
@@ -195,7 +195,7 @@ describe('다른 곳 모두 로그아웃', () => {
const others = otherSessions(sessions)
expect(others.map((item) => item.id)).toEqual(['a', 'b'])
expect(endOtherSessionsMessage(others)).toBe(
- '이 브라우저를 뺀 2곳(Edge · Windows, Safari · iOS)의 웹메일 로그인을 끊습니다. 끊긴 곳은 다음 동작에서 바로 로그인 화면으로 돌아갑니다.',
+ '이 브라우저를 뺀 2곳(Edge · Windows, Safari · iOS)의 웹메일에서 로그아웃합니다. 그곳에서 다음에 무엇이든 누르면 바로 로그인 화면이 나옵니다.',
)
const many = [
@@ -205,7 +205,7 @@ describe('다른 곳 모두 로그아웃', () => {
session({ id: 'e', device: null }),
]
expect(endOtherSessionsMessage(many)).toBe(
- '이 브라우저를 뺀 5곳(Edge · Windows, Safari · iOS, Firefox · Linux 외 2곳)의 웹메일 로그인을 끊습니다. 끊긴 곳은 다음 동작에서 바로 로그인 화면으로 돌아갑니다.',
+ '이 브라우저를 뺀 5곳(Edge · Windows, Safari · iOS, Firefox · Linux 외 2곳)의 웹메일에서 로그아웃합니다. 그곳에서 다음에 무엇이든 누르면 바로 로그인 화면이 나옵니다.',
)
})
@@ -213,7 +213,7 @@ describe('다른 곳 모두 로그아웃', () => {
const unknown = [session({ id: 'a', device: { browser: 'Edge', os: 'Windows' } }), session({ id: 'b', device: null })]
expect(endOtherSessionsMessage(unknown, true)).toBe(
- '이 브라우저를 알아보지 못해 이 브라우저를 포함한 2곳(Edge · Windows, 알 수 없는 브라우저)의 웹메일 로그인을 끊습니다. 이 브라우저도 로그아웃되어 로그인 화면으로 돌아갑니다.',
+ '이 브라우저를 알아보지 못해서 이 브라우저를 포함한 2곳(Edge · Windows, 알 수 없는 브라우저)의 웹메일에서 로그아웃합니다. 이 브라우저도 로그아웃되어 로그인 화면으로 돌아갑니다.',
)
})
})
diff --git a/frontend/src/shared/utils/account-security.ts b/frontend/src/shared/utils/account-security.ts
index fba5fe5..4bdee30 100644
--- a/frontend/src/shared/utils/account-security.ts
+++ b/frontend/src/shared/utils/account-security.ts
@@ -71,7 +71,7 @@ export function isMobileDevice(device: LoginDevice | null): boolean {
return MOBILE_OS.has(device?.os ?? '')
}
-/** 2단계 인증 상태 글 — 「꺼짐」 · 「켜짐 — 2026년 10월 31일부터」(켠 때를 모르면 「켜짐」) */
+/** 2단계 인증 상태 글 — 「꺼짐」 · 「2026년 10월 31일부터 켜짐」(켠 때를 모르면 「켜짐」) */
export function twoFactorStateLabel(status: TwoFactorStatus): string {
if (!status.isEnabled) return TWO_FACTOR_TEXT.off
const day = status.enabledAt ? formatLongDate(status.enabledAt) : ''
@@ -127,7 +127,7 @@ export function loginDeviceLabel(event: LoginEvent): string | null {
}
/**
- * 로그인 기록의 결과 글 — 「성공」 · 「성공 · 2단계 인증」 · 「실패 — 비밀번호 틀림」 · 「실패 — 코드 틀림」 · 「실패 — 앱 비밀번호 필요」.
+ * 로그인 기록의 결과 글 — 「성공」 · 「2단계 인증으로 성공」 · 「비밀번호가 틀려 실패」 · 「코드가 틀려 실패」 · 「앱 비밀번호가 필요해 실패」.
* 실패는 화면이 빨간 굵은 글자로만 보인다 (뱃지 없음)
*/
export function loginResultLabel(event: LoginEvent): { label: string; isFailure: boolean } {
diff --git a/frontend/src/shared/utils/large-file-status.spec.ts b/frontend/src/shared/utils/large-file-status.spec.ts
index 5e0b028..ed793a2 100644
--- a/frontend/src/shared/utils/large-file-status.spec.ts
+++ b/frontend/src/shared/utils/large-file-status.spec.ts
@@ -32,7 +32,7 @@ describe('읽기 화면의 대용량 첨부 줄 (디자인 원본 MailReadLargeF
it('검사 중 — 끝나는 날 대신 까닭, 받기는 막는다(aria-disabled) · 검사하지 못함은 받을 수 있다', () => {
expect(messageLargeFileView({ ...FILE, status: 'scanning' })).toEqual({
- meta: '1.2 GB · 보안 검사 중 — 끝나면 받을 수 있습니다',
+ meta: '1.2 GB · 보안 검사가 끝나면 받을 수 있습니다',
tone: 'normal',
download: 'blocked',
})
@@ -41,27 +41,27 @@ describe('읽기 화면의 대용량 첨부 줄 (디자인 원본 MailReadLargeF
it('위험은 빨간 줄 · 끝난 링크와 받을 수 없는 파일은 흐리게 — 받기 단추 없음', () => {
expect(messageLargeFileView({ ...FILE, status: 'infected' })).toEqual({
- meta: '위험 — 보안 검사에서 막았습니다. 받을 수 없습니다',
+ meta: '위험한 내용이 있어 보안 검사에서 막았고, 받을 수 없습니다',
tone: 'danger',
download: 'none',
})
expect(messageLargeFileView({ ...FILE, status: 'expired' })).toEqual({
- meta: '1.2 GB · 기간 지남 — 11월 29일까지였습니다',
+ meta: '1.2 GB · 받을 수 있는 기간이 11월 29일에 끝났습니다',
tone: 'dim',
download: 'none',
})
- expect(messageLargeFileView({ ...FILE, status: 'revoked' }).meta).toBe('1.2 GB · 공유 멈춤 — 보낸 사람이 링크를 끊었습니다')
+ expect(messageLargeFileView({ ...FILE, status: 'revoked' }).meta).toBe('1.2 GB · 보낸 사람이 링크를 끊어 받을 수 없습니다')
expect(messageLargeFileView({ ...FILE, status: 'revoked', downloads: { count: 3, lastDownloadedAt: null } }).meta).toBe(
- '1.2 GB · 3회 받음 · 공유 멈춤 — 링크를 끊었습니다',
+ '1.2 GB · 3회 받음 · 링크를 끊었습니다',
)
// 끊은 때(revokedAt)는 보낸 사람 자신에게만 날짜로 보인다(한국 날짜 — 디자인 원본 MailReadLargeFiles 셋째 줄). 받는 사람은 날짜 없이
const revokedAt = '2026-10-30T15:30:00Z'
expect(messageLargeFileView({ ...FILE, status: 'revoked', revokedAt }).meta).toBe(
- '1.2 GB · 공유 멈춤 — 보낸 사람이 링크를 끊었습니다',
+ '1.2 GB · 보낸 사람이 링크를 끊어 받을 수 없습니다',
)
expect(
messageLargeFileView({ ...FILE, status: 'revoked', revokedAt, downloads: { count: 3, lastDownloadedAt: null } }).meta,
- ).toBe('1.2 GB · 3회 받음 · 공유 멈춤 — 10월 31일에 끊었습니다')
+ ).toBe('1.2 GB · 3회 받음 · 10월 31일에 링크를 끊었습니다')
expect(messageLargeFileView({ ...FILE, status: 'limit-reached' }).tone).toBe('dim')
expect(messageLargeFileView({ ...FILE, status: 'missing' }).download).toBe('none')
})
diff --git a/frontend/src/shared/utils/large-file.spec.ts b/frontend/src/shared/utils/large-file.spec.ts
index 860e0ce..843692d 100644
--- a/frontend/src/shared/utils/large-file.spec.ts
+++ b/frontend/src/shared/utils/large-file.spec.ts
@@ -92,7 +92,7 @@ describe('크기 · 진행 글', () => {
it('첨부 안내 — 일반 한도 뒤에 링크 안내를 붙인다 (디자인 원본 ComposeLargeFiles)', () => {
expect(composeAttachmentHint({ ...NORMAL, maxCount: 20 }, LIMITS)).toBe(
- '파일당 최대 20MB · 합계 25MB · 최대 20개 · 더 큰 파일은 링크로 보냅니다 (파일당 2GB · 10개까지)',
+ '파일당 최대 20MB · 합계 25MB · 최대 20개 · 더 큰 파일은 파일당 2GB, 10개까지 링크로 보냅니다',
)
})
@@ -166,7 +166,7 @@ describe('[보내기]를 막는 까닭 (계약 4절 "보내기를 막는 까닭"
it('올리는 중 — 일반 첨부와 대용량 파일을 합한 수', () => {
expect(sendBlockOf({ ...clear, normalUploading: 1, largeActive: 1 })).toEqual({
code: 'large-file-uploading',
- text: '파일 2개를 올리는 중입니다 — 다 올리면 보낼 수 있습니다.',
+ text: '파일 2개를 올리는 중입니다. 다 올린 뒤에 보낼 수 있습니다.',
})
expect(sendBlockOf({ ...clear, normalUploading: 1 })?.code).toBe('attachment-uploading')
})
@@ -175,11 +175,11 @@ describe('[보내기]를 막는 까닭 (계약 4절 "보내기를 막는 까닭"
const all = { ...clear, largeActive: 1, largeIncomplete: true, largeInfected: true, largeCount: 11 }
expect(sendBlockOf(all)).toEqual({
code: 'large-file-infected',
- text: '보낼 수 없는 파일이 있습니다 — 빨간 줄의 파일을 빼 주세요.',
+ text: '보낼 수 없는 파일이 있습니다. 빨간 줄의 파일을 빼 주세요.',
})
expect(sendBlockOf({ ...all, largeInfected: false })).toEqual({
code: 'large-file-incomplete',
- text: '다 올리지 못한 파일이 있습니다 — 이어 올리거나 빼 주세요.',
+ text: '다 올리지 못한 파일이 있습니다. 이어 올리거나 빼 주세요.',
})
expect(sendBlockOf({ ...all, largeInfected: false, largeIncomplete: false })?.code).toBe('large-file-count')
expect(sendBlockOf({ ...clear, largeMissing: true })?.code).toBe('large-file-missing')
diff --git a/frontend/src/shared/utils/large-file.ts b/frontend/src/shared/utils/large-file.ts
index c6c213d..854499e 100644
--- a/frontend/src/shared/utils/large-file.ts
+++ b/frontend/src/shared/utils/large-file.ts
@@ -104,7 +104,7 @@ export function formatSizeLimit(bytes: number): string {
}
/**
- * 첨부 안내 — "파일당 최대 20MB · 합계 25MB · 최대 20개 · 더 큰 파일은 링크로 보냅니다 (파일당 2GB · 10개까지)".
+ * 첨부 안내 — "파일당 최대 20MB · 합계 25MB · 최대 20개 · 더 큰 파일은 파일당 2GB, 10개까지 링크로 보냅니다".
* 일반 첨부의 형식 설명(문서, 이미지, 압축 파일)은 링크 안내로 바꾼다 (디자인 원본 ComposeLargeFiles)
*/
export function composeAttachmentHint(
diff --git a/frontend/src/shared/utils/mail-bulk.spec.ts b/frontend/src/shared/utils/mail-bulk.spec.ts
index be82839..e54b769 100644
--- a/frontend/src/shared/utils/mail-bulk.spec.ts
+++ b/frontend/src/shared/utils/mail-bulk.spec.ts
@@ -66,10 +66,10 @@ describe('mail-bulk (여러 통 처리 보조)', () => {
expect(restoredMessageText(1_234)).toBe('메일 1,234통을 되돌렸습니다.')
})
- it('스팸 아님 — "메일 1통을 받은편지함으로 옮겼습니다 (스팸 아님)." · 일부만 처리했으면 그대로 둔 수를 덧붙인다', () => {
- expect(notSpamMovedText(1, '받은편지함')).toBe('메일 1통을 받은편지함으로 옮겼습니다 (스팸 아님).')
+ it('스팸 아님 — "메일 1통을 스팸이 아닌 메일로 표시해 받은편지함으로 옮겼습니다." · 일부만 처리했으면 그대로 둔 수를 덧붙인다', () => {
+ expect(notSpamMovedText(1, '받은편지함')).toBe('메일 1통을 스팸이 아닌 메일로 표시해 받은편지함으로 옮겼습니다.')
expect(notSpamMovedText(2, '받은편지함', 1)).toBe(
- '메일 2통을 받은편지함으로 옮겼습니다 (스팸 아님). 1통은 그사이 옮겨졌거나 지워져 그대로 두었습니다.',
+ '메일 2통을 스팸이 아닌 메일로 표시해 받은편지함으로 옮겼습니다. 1통은 그사이 옮겨졌거나 지워져 그대로 두었습니다.',
)
})
})
diff --git a/frontend/src/shared/utils/mail-filter-run.spec.ts b/frontend/src/shared/utils/mail-filter-run.spec.ts
index 08331fa..dcfed35 100644
--- a/frontend/src/shared/utils/mail-filter-run.spec.ts
+++ b/frontend/src/shared/utils/mail-filter-run.spec.ts
@@ -119,25 +119,25 @@ describe('알림 문구 (디자인 원본 MailFilterApplyStates)', () => {
const copy = mailFilterRunCopy(rule())
it('하는 중 — "· 확인 / 전체통 확인", 전체를 모르면 수를 뺀다', () => {
- expect(copy.progress({ scanned: 4200, total: 8412 })).toBe('‘거래처 메일’ 필터를 받은편지함에 적용하는 중 · 4,200 / 8,412통 확인')
- expect(copy.progress({ scanned: 0, total: 0 })).toBe('‘거래처 메일’ 필터를 받은편지함에 적용하는 중')
+ expect(copy.progress({ scanned: 4200, total: 8412 })).toBe('‘거래처 메일’ 필터로 받은편지함을 정리하는 중 · 4,200 / 8,412통 확인')
+ expect(copy.progress({ scanned: 0, total: 0 })).toBe('‘거래처 메일’ 필터로 받은편지함을 정리하는 중')
})
it('끝 — N통에 적용, 0통이면 맞는 메일이 없다', () => {
- expect(copy.done(37)).toBe('‘거래처 메일’ 필터를 받은편지함의 메일 37통에 적용했습니다.')
- expect(copy.done(1234)).toBe('‘거래처 메일’ 필터를 받은편지함의 메일 1,234통에 적용했습니다.')
+ expect(copy.done(37)).toBe('‘거래처 메일’ 필터로 받은편지함의 메일 37통을 정리했습니다.')
+ expect(copy.done(1234)).toBe('‘거래처 메일’ 필터로 받은편지함의 메일 1,234통을 정리했습니다.')
expect(copy.done(0)).toBe('받은편지함에 ‘거래처 메일’ 필터에 맞는 메일이 없습니다.')
})
it('멈춤 — 전체 가운데 확인한 데까지 · 적용한 수', () => {
expect(copy.stopped({ scanned: 4200, matched: 12, total: 8412 })).toBe(
- '‘거래처 메일’ 필터 — 8,412통 가운데 4,200통까지 확인하고 멈췄습니다(12통에 적용). 다시 시도해 주세요.',
+ '‘거래처 메일’ 필터로 8,412통 가운데 4,200통까지 확인하고 멈췄습니다. 그동안 12통을 정리했습니다. 다시 시도해 주세요.',
)
})
it('이름 없는 필터는 조건 글이 이름 자리에 — 작은따옴표가 겹치지 않게 큰따옴표로 묶는다', () => {
expect(mailFilterRunCopy(rule({ name: '' })).done(3)).toBe(
- '“보낸 사람에 ‘@hanbit-partners.co.kr’ 포함” 필터를 받은편지함의 메일 3통에 적용했습니다.',
+ '“보낸 사람에 ‘@hanbit-partners.co.kr’ 포함” 필터로 받은편지함의 메일 3통을 정리했습니다.',
)
})
})
diff --git a/frontend/src/shared/utils/mail-html.spec.ts b/frontend/src/shared/utils/mail-html.spec.ts
index 56121c0..3fc2982 100644
--- a/frontend/src/shared/utils/mail-html.spec.ts
+++ b/frontend/src/shared/utils/mail-html.spec.ts
@@ -250,7 +250,7 @@ describe('대용량 첨부 덩이 접기 (11단계 — 디자인 원본 MailRead
expect(details).not.toBeNull()
expect(details?.hasAttribute('open')).toBe(false)
expect(details?.querySelector('summary')?.textContent).toContain('본문의 대용량 첨부 안내')
- expect(details?.querySelector('summary')?.textContent).toContain('(접어 둠) — 파일은 아래 첨부 칸에 있습니다')
+ expect(details?.querySelector('summary')?.textContent).toContain('파일은 아래 첨부 칸에 있어 접어 두었습니다')
expect(details?.querySelector('table a')?.getAttribute('href')).toBe('https://files.arasystem.it.kr/s/abc')
expect(body.innerHTML).not.toContain('data-ara-large-files')
// 덩이는 제자리 — 내 글 아래, 인용 위
diff --git a/frontend/src/shared/utils/mail-realtime.spec.ts b/frontend/src/shared/utils/mail-realtime.spec.ts
index 33b891c..ca72064 100644
--- a/frontend/src/shared/utils/mail-realtime.spec.ts
+++ b/frontend/src/shared/utils/mail-realtime.spec.ts
@@ -13,14 +13,14 @@ import {
describe('realtimeLinkText (설정 › 알림의 실시간 연결 줄)', () => {
it('연결됨은 초록 점과 "연결되어 있습니다", 끊김 · 지원 안 함은 "연결할 수 없어 1분마다 확인합니다."', () => {
expect(realtimeLinkText('live')).toMatchObject({
- text: '연결되어 있습니다 — 새 메일이 오면 바로 목록에 나타납니다.',
+ text: '연결되어 있어 새 메일이 오면 바로 목록에 나타납니다.',
notice: null,
live: true,
})
expect(realtimeLinkText('down')).toEqual({
text: '연결할 수 없어 1분마다 확인합니다.',
notice: '연결될 때까지 바탕화면 알림은 오지 않습니다.',
- hint: '저절로 다시 잇습니다. 회사 방화벽 · 프록시가 오래 열린 연결을 끊으면 이렇게 됩니다.',
+ hint: '저절로 다시 연결합니다. 회사 방화벽이나 프록시가 오래 열린 연결을 끊으면 이렇게 될 수 있습니다.',
live: false,
})
expect(realtimeLinkText('unsupported')).toEqual({
diff --git a/frontend/src/shared/utils/mail-send-result.spec.ts b/frontend/src/shared/utils/mail-send-result.spec.ts
index bd1ac5a..b20d59a 100644
--- a/frontend/src/shared/utils/mail-send-result.spec.ts
+++ b/frontend/src/shared/utils/mail-send-result.spec.ts
@@ -12,8 +12,8 @@ describe('partialSendMessage — 받는 사람 일부가 받지 못한 발송
expect(partialSendMessage([])).toBeNull()
})
- it('1명 — "보냈지만 1명은 받지 못했습니다 — 주소 (까닭)"', () => {
- expect(partialSendMessage([NOBODY])).toBe('보냈지만 1명은 받지 못했습니다 — nobody@arasystem.it.kr (없는 주소)')
+ it('1명 — "보냈지만 1명은 받지 못했습니다. 주소 (까닭)"', () => {
+ expect(partialSendMessage([NOBODY])).toBe('보냈지만 1명은 받지 못했습니다. nobody@arasystem.it.kr (없는 주소)')
})
it.each<[RejectedRecipientReason, string]>([
@@ -22,29 +22,29 @@ describe('partialSendMessage — 받는 사람 일부가 받지 못한 발송
['temporary', '지금은 받을 수 없음'],
])('까닭 %s 는 "%s"', (reason, text) => {
expect(partialSendMessage([{ email: 'a@arasystem.it.kr', reason }])).toBe(
- `보냈지만 1명은 받지 못했습니다 — a@arasystem.it.kr (${text})`,
+ `보냈지만 1명은 받지 못했습니다. a@arasystem.it.kr (${text})`,
)
})
it('2명 — 두 사람 모두 "주소 (까닭)"를 ", " 로 잇는다', () => {
expect(partialSendMessage([NOBODY, REFUSED])).toBe(
- '보냈지만 2명은 받지 못했습니다 — nobody@arasystem.it.kr (없는 주소), ceo@partner.example (받는 쪽이 거절)',
+ '보냈지만 2명은 받지 못했습니다. nobody@arasystem.it.kr (없는 주소), ceo@partner.example (받는 쪽이 거절)',
)
})
it('3명 이상 — 앞의 두 명만 적고 나머지는 " 외 N명"', () => {
expect(partialSendMessage([NOBODY, REFUSED, BUSY])).toBe(
- '보냈지만 3명은 받지 못했습니다 — nobody@arasystem.it.kr (없는 주소), ceo@partner.example (받는 쪽이 거절) 외 1명',
+ '보냈지만 3명은 받지 못했습니다. nobody@arasystem.it.kr (없는 주소), ceo@partner.example (받는 쪽이 거절) 외 1명',
)
const many = Array.from({ length: 5 }, (_, index) => ({ ...BUSY, email: `user${index + 1}@arasystem.it.kr` }))
expect(partialSendMessage(many)).toBe(
- '보냈지만 5명은 받지 못했습니다 — user1@arasystem.it.kr (지금은 받을 수 없음), user2@arasystem.it.kr (지금은 받을 수 없음) 외 3명',
+ '보냈지만 5명은 받지 못했습니다. user1@arasystem.it.kr (지금은 받을 수 없음), user2@arasystem.it.kr (지금은 받을 수 없음) 외 3명',
)
})
it.each(['bounced', 'constructor'])('계약에 없는 까닭(%s)이면 까닭 없이 주소만 적는다', (reason) => {
const recipient: RejectedRecipient = { email: NOBODY.email, reason: reason as string as RejectedRecipientReason }
- expect(partialSendMessage([recipient])).toBe('보냈지만 1명은 받지 못했습니다 — nobody@arasystem.it.kr')
+ expect(partialSendMessage([recipient])).toBe('보냈지만 1명은 받지 못했습니다. nobody@arasystem.it.kr')
})
})
@@ -53,19 +53,19 @@ describe('partialSendMessage — 엔진이 붙잡아 둔 발송 (9단계 "보내
it('보내기 취소를 기다리는 중이면 아직 나가지 않았다 — "받지 못합니다" (디자인 원본 MailSendLater)', () => {
expect(partialSendMessage([NOBODY], { pendingKind: 'undo' })).toBe(
- '보냈지만 1명은 받지 못합니다 — nobody@arasystem.it.kr (없는 주소)',
+ '보냈지만 1명은 받지 못합니다. nobody@arasystem.it.kr (없는 주소)',
)
})
it('예약이면 "예약했지만 … 받지 못합니다"', () => {
expect(partialSendMessage([NOBODY], { pendingKind: 'scheduled' })).toBe(
- '예약했지만 1명은 받지 못합니다 — nobody@arasystem.it.kr (없는 주소)',
+ '예약했지만 1명은 받지 못합니다. nobody@arasystem.it.kr (없는 주소)',
)
})
it('바로 보냈으면(null) 지금처럼 "받지 못했습니다"', () => {
expect(partialSendMessage([NOBODY], { pendingKind: null })).toBe(
- '보냈지만 1명은 받지 못했습니다 — nobody@arasystem.it.kr (없는 주소)',
+ '보냈지만 1명은 받지 못했습니다. nobody@arasystem.it.kr (없는 주소)',
)
})
})
diff --git a/frontend/src/shared/utils/mail-vacation.spec.ts b/frontend/src/shared/utils/mail-vacation.spec.ts
index e562145..4e27650 100644
--- a/frontend/src/shared/utils/mail-vacation.spec.ts
+++ b/frontend/src/shared/utils/mail-vacation.spec.ts
@@ -148,7 +148,7 @@ describe('알리는 글', () => {
expect(vacationSavedText(ranged(null, null), now)).toBe('저장했습니다. 끌 때까지 자동으로 답합니다.')
expect(vacationSavedText(ranged(null, '2026-11-03'), now)).toBe('저장했습니다. 11월 3일(화)까지 자동으로 답합니다.')
expect(vacationSavedText(ranged('2026-10-30', null), now)).toBe('저장했습니다. 10월 30일(금)부터 자동으로 답합니다.')
- expect(vacationSavedText(OFF, now)).toBe('저장했습니다. 자동 응답을 껐습니다 — 써 둔 글은 남겨 둡니다.')
+ expect(vacationSavedText(OFF, now)).toBe('저장했습니다. 자동 응답을 껐고, 써 둔 글은 그대로 남아 있습니다.')
})
it('올해가 아니면 연도를 붙인다', () => {
diff --git a/frontend/src/shared/utils/mailbox-operation.spec.ts b/frontend/src/shared/utils/mailbox-operation.spec.ts
index 9d96428..1b0c072 100644
--- a/frontend/src/shared/utils/mailbox-operation.spec.ts
+++ b/frontend/src/shared/utils/mailbox-operation.spec.ts
@@ -10,15 +10,15 @@ describe('mailboxOperationCopy', () => {
expect(copy.progress(600, 1234)).toBe('받은편지함의 메일을 읽음으로 표시하는 중 · 600 / 1,234통')
expect(copy.done(1234)).toBe('받은편지함의 메일 1,234통을 읽음으로 표시했습니다.')
- expect(copy.failed(600, 1234)).toBe('1,234통 가운데 600통까지 처리했습니다 — 다시 시도해 주세요.')
+ expect(copy.failed(600, 1234)).toBe('1,234통 가운데 600통까지 처리하고 멈췄습니다. 다시 시도해 주세요.')
// 다른 탭이 먼저 했다
expect(copy.done(0)).toBe('받은편지함에 안 읽은 메일이 없습니다.')
})
it('비우기 — 메일함 이름의 받침에 맞춰 을/를', () => {
expect(mailboxOperationCopy('empty', '휴지통').progress(20, 21)).toBe('휴지통을 비우는 중 · 20 / 21통')
- expect(mailboxOperationCopy('empty', '휴지통').done(21)).toBe('휴지통을 비웠습니다 — 메일 21통.')
- expect(mailboxOperationCopy('empty', '스팸메일함').done(5)).toBe('스팸메일함을 비웠습니다 — 메일 5통.')
+ expect(mailboxOperationCopy('empty', '휴지통').done(21)).toBe('메일 21통을 지워 휴지통을 비웠습니다.')
+ expect(mailboxOperationCopy('empty', '스팸메일함').done(5)).toBe('메일 5통을 지워 스팸메일함을 비웠습니다.')
// 다른 탭이 먼저 비웠다 — 받침에 맞춰 이/가
expect(mailboxOperationCopy('empty', '휴지통').done(0)).toBe('휴지통이 이미 비어 있습니다.')
expect(mailboxOperationCopy('empty', 'Spam').done(0)).toBe('Spam이(가) 이미 비어 있습니다.')
diff --git a/frontend/src/shared/utils/share-links.spec.ts b/frontend/src/shared/utils/share-links.spec.ts
index 7674afb..81b30c8 100644
--- a/frontend/src/shared/utils/share-links.spec.ts
+++ b/frontend/src/shared/utils/share-links.spec.ts
@@ -41,13 +41,13 @@ describe('인용 속 링크의 상태 → 칸 아래 글 (디자인 README "11
it('받을 수 있는 링크 — 원래 메일의 파일 수와 끝나는 날(한국)', () => {
expect(quotedLinkNotices([{ url: URL, status: 'active', expiresAt: '2026-11-29T14:59:59.999Z' }], names)).toEqual([
- { key: URL, tone: 'info', text: '원래 메일의 대용량 첨부 2개는 인용 속 링크로 함께 갑니다 — 11월 29일까지' },
+ { key: URL, tone: 'info', text: '원래 메일의 대용량 첨부 2개는 인용 속 링크로 함께 갑니다. 11월 29일까지 받을 수 있습니다.' },
])
})
it('파일 수를 모르면 수 없이', () => {
expect(quotedLinkNotices([{ url: OTHER, status: 'active', expiresAt: '2026-11-29T14:59:59.999Z' }], names)[0]?.text).toBe(
- '인용 속 대용량 첨부 링크는 함께 갑니다 — 11월 29일까지',
+ '인용 속 대용량 첨부 링크도 함께 갑니다. 11월 29일까지 받을 수 있습니다.',
)
})
diff --git a/frontend/src/stores/toast.store.spec.ts b/frontend/src/stores/toast.store.spec.ts
index 4238339..caed581 100644
--- a/frontend/src/stores/toast.store.spec.ts
+++ b/frontend/src/stores/toast.store.spec.ts
@@ -228,8 +228,8 @@ describe('toast.store', () => {
const failed = toast.progress('비우는 중')
store.dismiss(store.toasts[1]?.id ?? -1)
- failed.fail('21통 가운데 10통까지 처리했습니다 — 다시 시도해 주세요.')
- expect(store.toasts.at(-1)).toMatchObject({ kind: 'error', message: '21통 가운데 10통까지 처리했습니다 — 다시 시도해 주세요.' })
+ failed.fail('21통 가운데 10통까지 처리하고 멈췄습니다. 다시 시도해 주세요.')
+ expect(store.toasts.at(-1)).toMatchObject({ kind: 'error', message: '21통 가운데 10통까지 처리하고 멈췄습니다. 다시 시도해 주세요.' })
})
})
--
2.52.0
From cda4093ffd06ddd927839bf906ec36a2c40d91b4 Mon Sep 17 00:00:00 2001
From: ttipo
Date: Thu, 5 Nov 2026 12:44:43 +0900
Subject: [PATCH 2/6] =?UTF-8?q?docs:=20=ED=99=94=EB=A9=B4=20=EB=AC=B8?=
=?UTF-8?q?=EA=B5=AC=20=EB=A7=90=ED=88=AC=20=EA=B7=9C=EC=B9=99=EC=9D=84=20?=
=?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20README=20=C2=B7=20vue3=20?=
=?UTF-8?q?=EC=8A=A4=ED=82=AC=EC=97=90=20=EC=A0=81=EB=8A=94=EB=8B=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
README 에 「화면 문구 — 사용자에게 말하듯」 절(규칙 여덟과 예시)을 두고, README 가 옮겨 적은 화면 문구를
새 글로 맞춘다. vue3 스킬 점검 목록에 문구 줄을 더한다. 보드(*.dc.html)의 글은 11-05 전 것으로 둔다.
Co-Authored-By: Claude Opus 5.5
---
.claude/skills/vue3/SKILL.md | 1 +
docs/design/webmail/README.md | 351 ++++++++++++++++++----------------
2 files changed, 190 insertions(+), 162 deletions(-)
diff --git a/.claude/skills/vue3/SKILL.md b/.claude/skills/vue3/SKILL.md
index f95d652..b7b6feb 100644
--- a/.claude/skills/vue3/SKILL.md
+++ b/.claude/skills/vue3/SKILL.md
@@ -262,6 +262,7 @@ router.beforeEach((to) => {
- [ ] **에러 사전**: `error-codes.generated.ts` 직접 수정 금지 — 백엔드 레지스트리 수정 후 `npm run sync:error-codes`.
- [ ] **목록 화면**: 응답은 `{items, pageInfo}` — `shared/types/pagination.ts` 타입 사용.
- [ ] **컴포넌트**: `
+
+
+
+
+
+
+