@channel.io/lokalise-mcp
v0.1.0
Published
Model Context Protocol server for Lokalise integration
Readme
Lokalise MCP
Channel 팀을 위한 Lokalise MCP 도구입니다. 이 도구는 Cursor에서 번역 키 생성 및 관리를 도와주는 AI 도구입니다.
Cursor MCP 설정
Cursor에서 MCP로 사용하려면 .cursor/mcp.json 파일에 다음과 같이 설정합니다:
{
"mcpServers": {
"lokalise-mcp": {
"command": "npx -y @channel.io/lokalise-mcp",
"env": {
"LOKALISE_TOKEN": "your-lokalise-token",
"PROJECT_ID": "your-project-id",
"PLATFORMS": ["web", "ios", "android"]
}
}
}
}환경 변수 설명
- LOKALISE_TOKEN: Lokalise API에 접근하기 위한 개인 액세스 토큰입니다. Lokalise 계정 설정에서 발급받을 수 있습니다.
- PROJECT_ID: 작업할 Lokalise 프로젝트의 고유 식별자입니다. Lokalise 프로젝트 URL이나 설정에서 확인할 수 있습니다.
- PLATFORMS: 번역 키가 지원할 플랫폼 목록입니다.
주요 기능
이 MCP 서버는 두 가지 주요 기능을 제공합니다:
- 번역 키 조회: 프로젝트에 이미 존재하는 번역 키를 검색합니다
- 번역 키 생성: 새로운 번역 키를 생성하고 초기 번역값을 설정합니다
함께 사용하면 좋은 Cursor Rule 예시
아래의 프롬프트를 사용하면 효과적으로 번역 키 추가 작업을 진행할 수 있습니다:
이 프롬프트는 ch-desk-web에서 사용하는 프롬프트입니다. 다른 프로젝트에서 사용하려면 프롬프트를 일부 수정해야 할 수 있습니다.
당신은 cursor 내에 통합된 LLM 에이전트입니다. 하드코딩된 한글 문자열에 대한 번역 키 추가 작업을 자동화하기 위해 아래 가이드라인을 준수하세요.
## 목적
- 코드베이스에서 하드코딩된 한글 문자열을 찾아 대응하는 번역 키를 생성 및 적용합니다.
- 기본적으로 <업무 파이프라인>을 따라 작업을 진행합니다.
## [!CRITICAL] 제약 조건
- <업무 파이프라인>의 단계를 반드시 지키도록
- 만약 <업무 파이프라인>의 특정 단계에서 작업 수행을 실패하면 그 즉시 실패 원인과 함께 응답 종료
## [!CRITICAL] 필수 참고 사항
### 명령어 정의
- '/completion'이란 현재 챗을 완료하고 사용자에게 추가 프롬프트를 요청할 때 사용하는 명령어입니다.
### MCP tool 주의 사항
- lokalise_create_keys를 통한 번역키 생성은 비가역적으로 진행되며, 이미 존재하는 키를 삭제하거나 수정할 수 없음
- lokalise_create_keys 전에 lokalise_get_keys를 통한 중복 확인 단계를 수행하지 않으면 중복 키 생성 시 오류가 발생할 수 있음
### 번역 키 네이밍 규칙
- snake_case.snake_case.snake_case 네이밍 사용
- 계층 구조 준수: 코드베이스의 구조를 참조하여, 위계와 맥락에 따라 번역키에 적절히 이름을 붙이기
## 업무 파이프라인
### 1. 작업 맥락 파악
- 사용자가 참조로 제공한 "작업 범위" 내에서 하드코딩된 한글 문자열 검색 (주석 처리된 문자열 제외)
- 번역이 필요한 페이지/컴포넌트의 구조와 문맥 파악
### 2. 번역 키:값 준비
- <번역 키 네이밍 규칙>에 따라 `{ "key": "value" }` 형식으로 번역 키:값 목록 준비 (`value`는 원본 한글)
- [!IMPORTANT] 특수 요소 처리:
* 템플릿 리터럴(`${}`) → `import { sprintf } from 'sprintf-js'`에서 parse할 수 있는 형식으로 변환 (e.g. `"${name}님 안녕하세요"` → `"%s님 안녕하세요"`)
* HTML 태그 → 번역 값에 직접 innerHTML로 처리 (e.g. `제 이름은 <b>라온</b>입니다.`)
* 줄바꿈 등 특수 문자 → 적절히 이스케이프 시퀀스 활용
### 3. 번역 값 중복 확인
- 동일한 번역 값(`value`)을 가진 키가 있는지 확인
- [!IMPORTANT] 리포지토리 루트에 있는 `l10n/ko.json` 파일에서 batch로 grep 하여 추출
- 중복이 없다면 최종 번역 키:값 목록 준비 후 다음 단계로 넘어감
- 중복 발견 시 옵션 제안:
* 기존 키 재사용 (도메인 로직이 아니라 일반적인 번역 키인 경우)
* 새 키 생성 (도메인 의존적인 번역 키인 경우)
- [!CRITICAL] 옵션에 대한 제안은 하되, 최종 결정은 사용자에게 물어볼 것 (/completion)
- 가독성 있게 코드블록(json)으로 제안할 것
- 사용자의 답변을 바탕으로 최종 `{ "key": "value" }` 형식의 번역 키:값 목록 준비
### 4. 번역 키 생성 전 품질 검증
- "작업 범위" 내 놓친 번역 키가 있는지 확인
- 이전 단계가 모두 정상적으로 수행되었는지 확인
### 5. 번역 키 생성 및 통합
- `lokalise_get_keys` 도구로 번역키 중복 확인
- [!CRITICAL] 중복 키가 있는 경우:
* 키에 version postfix를 붙여 중복 회피 (e.g. `meet.meet_banner.title` → `meet.meet_banner.title.v1`)
* 사용자에게 다시 최종 결정을 물어볼 것 (/completion)
- `lokalise_create_keys` 도구로 키 생성
- [!CRITICAL] 사용자에게 번역 키 태그를 한번도 받지 못한 경우 사용자에게 직접 요청할 것 (/completion)
- `yarn download-l10n` 스크립트 실행하여 번역 파일 갱신
- 코드 적용:
* `import useTranslator from 'Hooks/useTranslator'` 훅을 사용한 번역 키 적용
* 기본적으로 translate 함수를 사용
* 템플릿 변수 처리해야하는 경우, 함수의 파라미터를 적절히 사용
* innerHTML 처리해야하는 경우, translate 대신 translateWithInnerHtml 함수를 사용
## 번역 키:값 예시
### 일반적인 번역 키:값
```json
{
"channel_settings.manage_webhooks.webhook_list.empty": "Webhook이 없습니다. 새 Webhook을 만들어 주세요."
"channel_settings.manage_webhooks.show_webhook.modal.title": "새 Webhook이 만들어졌습니다."
"channel_settings.manage_webhooks.show_webhook.modal.ok": "확인"
"channel_settings.manage_webhooks.edit_webhook.modal.title": "Webhook 관리"
"channel_settings.manage_webhooks.delete_webhook.success": "Webhook이 삭제되었습니다."
"channel_settings.plugin_setting.button_customize": "버튼 커스터마이징"
"channel_settings.plugin_setting.button_customize.reset": "초기화"
"channel_settings.integrations.messengers.line.apply_modal.locale": "언어"
"channel_settings.integrations.messengers.line.apply_modal.locale.description": "선택하신 언어에 따라 고객에게 부재중 메시지와 같은 자동 메시지가 전송됩니다."
"channel_settings.integrations.messengers.line.apply_modal.channel_id": "라인 Channel ID"
"channel_settings.integrations.messengers.line.apply_modal.channel_id.placeholder": "라인 Channel ID 입력"
"channel_settings.integrations.messengers.line.apply_modal.channel_secret": "라인 Channel Secret"
"channel_settings.integrations.messengers.line.apply_modal.channel_secret.placeholder": "라인 Channel Secret 입력"
"channel_settings.integrations.messengers.line.apply_modal.access_token": "Access token\n(long-lived)"
"channel_settings.integrations.messengers.line.apply_modal.access_token.placeholder": "Access token 입력"
"channel_settings.integrations.messengers.line.apply_modal.id": "Basic ID (또는 Premium ID)"
"channel_settings.integrations.messengers.line.apply_modal.id.placeholder": "Basic ID 또는 Premium ID를 입력"
"channel_settings.integrations.messengers.line.webhook_link_modal.description": "아래 링크를 복사하여 라인 Channel의 Messaging API settings - Webhook URL에 입력하고 Webhook을 활성화하면 연동이 완료됩니다."
}
```
### with common prefix
```json
{
"common.cancel": "취소"
"common.confirm": "확인"
"common.close": "닫기"
"common.delete": "삭제"
"common.save": "저장"
"common.copy": "복사"
"common.apply": "적용"
"common.me": "나"
"common.next": "다음"
"common.retry": "재시도"
"common.time": "시간"
"common.no_data": "표시할 내용이 없습니다."
"common.not_empty": "비워둘 수 없습니다"
}
```
### with error prefix
```json
{
"error.do_not_empty": "비워둘 수 없습니다."
"error.max_length": "%s자까지 입력할 수 있습니다."
"error.available_english_number": "영문, 숫자, -, _만 사용할 수 있습니다."
"error.aspect_ratio": "가로:세로 비율이 %s이 아닌 경우 등록할 수 없습니다."
"error.min_width": "가로 %spx 이하일 경우 등록할 수 없습니다."
"error.max_file_size": "최대 %s까지 등록할 수 있습니다."
"error.content_type": "%s만 등록할 수 있습니다."
"error.require_version_update.title": "앗, 이전 버전의 앱을 사용 중이에요"
"error.unsupported_file_extension": "파일 형식을 다시 확인해주세요."
"error.invalid_url": "잘못된 주소입니다."
}
```
### with innerHTML & escape sequence
```json
{
"log_message.create_chat": "<b>%(subject)s<\/b>님이 <b>%(object)s<\/b>(을)를 만들었습니다."
"log_message.invite_chat": "<b>%(subject)s<\/b>님이 <b>%(object)s<\/b>을(를) 초대했습니다."
"common.powered_by": "<b>채널톡<\/b> 이용중"
"channel.invite_recommend_description": "현재 채널에 <b>%s명<\/b>의 매니저가 있습니다. <b>매니저를 추가해도 추가 과금이 없으니<\/b> 안심하고 매니저를 초대하여 대화해보세요."
"campaign_editor.ui.operating_panel.activated_description": "언제, 어떻게 메시지를 전송할지 설정합니다. '운영' 설정을 끝으로 메시지가 전송됩니다.<br>\n예) 서울 접속자 중 가입하고 2주 동안 구매가 없으면 <b><u>매주 월요일 아침 9시에<\/u><\/b> 전송",
"campaign_editor.ui.delay_panel.activated_description": "아래 '대기 시간' 만큼 기다렸다가 다시 대상 고객을 체크하고 '운영' 단계로 넘어갑니다.<br>\n예) 서울 접속자 중 가입하고 <b><u>2주 동안<\/u><\/b> 구매가 없으면 매주 월요일 아침 9시에 전송"
}
```
### with template variable
```json
{
"chat_action.manager_list.header": "%s명 참여"
"confirm_delete_member": "%s님을 채널에서 삭제하시겠습니까?"
"invitation.sns.message": "%s님이 '%s' 채널로 당신을 초대했습니다.\n채널에 참여해 주시기 바랍니다."
"settings.billing.order_histories.discount_tooltip": "%s%% 할인 쿠폰 적용됨"
"support_bot.max": "최대 %s개까지 추가할 수 있습니다."
"bot.select.create_new_bot": "'%s' 새로 만들기..."
"teamchats.participants_list.title": "참여자 ∙ %s"
"expressive_query.values.format.plural": "%s 또는 %s개"
"common.append_new_value": "'%s' 입력"
"marketing.otm.success_modal.title_content": "%s명에게 메시지를 성공적으로 전달했습니다!👏"
"user_profile_page.menu.copy_link.tooltip": "유저 링크 복사\n%s"
"log_message.invite_chat": "<b>%(subject)s<\/b>님이 <b>%(object)s<\/b>을(를) 초대했습니다."
"log_message.leave_chat": "<b>%(subject)s<\/b>님이 대화를 떠났습니다."
"log_message.change_chat_name": "<b>%(subject)s<\/b>님이 그룹이름을 <b>%(object)s<\/b>(으)로 변경하였습니다."
"notification.upload_file.description": "%(name)s 님이 파일을 업로드했습니다."
"log_message.remove": "<b>%(subject)s<\/b>님이 상담을 삭제하였습니다."
"log_message.close": "<b>%(subject)s<\/b>님이 상담을 종료하였습니다."
"log_message.unhold": "<b>%(subject)s<\/b>님이 상담을 보류 해제 하였습니다."
}
```
