UNPKG

@channel.io/tolgee-mcp

Version:

Model Context Protocol server for Tolgee integration

212 lines (174 loc) 11.6 kB
# Tolgee MCP Channel 팀을 위한 Tolgee MCP 도구입니다. 이 도구는 Cursor에서 번역 키 생성 및 관리를 도와주는 AI 도구입니다. ## Cursor MCP 설정 Cursor에서 MCP로 사용하려면 `.cursor/mcp.json` 파일에 다음과 같이 설정합니다: ```json { "mcpServers": { "tolgee-mcp": { "command": "npx -y @channel.io/tolgee-mcp", "env": { "TOLGEE_TOKEN": "your-tolgee-token", "PROJECT_ID": "your-project-id", "PLATFORMS": ["web", "ios", "android"] } } } } ``` ### 환경 변수 설명 - **TOLGEE_TOKEN**: Tolgee API에 접근하기 위한 개인 액세스 토큰입니다. Tolgee 계정 설정에서 발급받을 수 있습니다. - **PROJECT_ID**: 작업할 Tolgee 프로젝트의 고유 식별자입니다. Tolgee 프로젝트 URL이나 설정에서 확인할 수 있습니다. - **PLATFORMS**: 번역 키가 지원할 플랫폼 목록입니다. ## 주요 기능 이 MCP 서버는 두 가지 주요 기능을 제공합니다: 1. **번역 키 조회**: 프로젝트에 이미 존재하는 번역 키를 검색합니다 2. **번역 키 생성**: 새로운 번역 키를 생성하고 초기 번역값을 설정합니다 ## 함께 사용하면 좋은 Cursor Rule 예시 아래의 프롬프트를 사용하면 효과적으로 번역 키 추가 작업을 진행할 수 있습니다: > 이 프롬프트는 ch-desk-web에서 사용하는 프롬프트입니다. 다른 프로젝트에서 사용하려면 프롬프트를 일부 수정해야 할 수 있습니다. ```` 당신은 cursor 내에 통합된 LLM 에이전트입니다. 하드코딩된 한글 문자열에 대한 번역 키 추가 작업을 자동화하기 위해 아래 가이드라인을 준수하세요. ## 목적 - 코드베이스에서 하드코딩된 한글 문자열을 찾아 대응하는 번역 키를 생성 및 적용합니다. - 기본적으로 <업무 파이프라인>을 따라 작업을 진행합니다. ## [!CRITICAL] 제약 조건 - <업무 파이프라인>의 단계를 반드시 지키도록 - 만약 <업무 파이프라인>의 특정 단계에서 작업 수행을 실패하면 그 즉시 실패 원인과 함께 응답 종료 ## [!CRITICAL] 필수 참고 사항 ### 명령어 정의 - '/completion'이란 현재 챗을 완료하고 사용자에게 추가 프롬프트를 요청할 때 사용하는 명령어입니다. ### MCP tool 주의 사항 - tolgee_create_keys를 통한 번역키 생성은 비가역적으로 진행되며, 이미 존재하는 키를 삭제하거나 수정할 수 없음 - tolgee_create_keys 전에 tolgee_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. 번역 키 생성 및 통합 - `tolgee_get_keys` 도구로 번역키 중복 확인 - [!CRITICAL] 중복 키가 있는 경우: * 키에 version postfix를 붙여 중복 회피 (e.g. `meet.meet_banner.title` → `meet.meet_banner.title.v1`) * 사용자에게 다시 최종 결정을 물어볼 것 (/completion) - `tolgee_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>님이 상담을 보류 해제 하였습니다." } ``` ````