Sunoのプロンプトが200文字までなので、DeepL API & 文字数カウンターを作ってみた

## はじめに

**DeepL API & 文字数カウンター**
https://gist.github.com/sarap422/b2c0fdff98b14197cc7fd41541b5cf34

Sunoのプロンプトは200文字までという制限があります。長文を分割するのは手間がかかるので、文字数をカウントしながら翻訳できるツールを作ってみました。DeepL API Freeを使用することで、月50万文字まで無料で高精度な翻訳が利用できます。

## DeepL API Freeの特徴

- 月間50万文字まで無料
- 高精度な機械翻訳
- シンプルなAPI
- 多言語対応（28言語以上）
- セキュアな通信（HTTPS必須）

## 実装手順

### 1. DeepL API Freeのアカウント作成

1. [DeepL API Free](https://www.deepl.com/pro#developer)にアクセス
2. アカウントを作成
3. APIキーを取得（形式: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:fx`）

### 2. CORSの問題を解決するためのプロキシ作成

ブラウザから直接DeepL APIを呼び出すとCORSエラーが発生するため、PHPでプロキシを作成します。

```php
<?php
// translation-proxy.php

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
header('Content-Type: application/json; charset=utf-8');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit();
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['error' => 'Method not allowed']);
    exit();
}

$json = file_get_contents('php://input');
$data = json_decode($json, true);

if (!isset($data['text']) || !isset($data['source_lang']) || 
    !isset($data['target_lang']) || !isset($data['api_key'])) {
    http_response_code(400);
    echo json_encode(['error' => 'Missing required parameters']);
    exit();
}

$url = 'https://api-free.deepl.com/v2/translate';
$params = http_build_query([
    'text' => $data['text'],
    'source_lang' => $data['source_lang'],
    'target_lang' => $data['target_lang']
]);

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => $url,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $params,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: DeepL-Auth-Key ' . $data['api_key'],
        'Content-Type: application/x-www-form-urlencoded'
    ],
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_TIMEOUT => 30
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if (curl_errno($ch)) {
    http_response_code(500);
    echo json_encode(['error' => 'Curl error: ' . curl_error($ch)]);
    curl_close($ch);
    exit();
}

curl_close($ch);
http_response_code($httpCode);
echo $response;
```

### 3. フロントエンド実装

HTMLでUIを作成し、JavaScriptで文字数カウントと翻訳機能を実装します。

```html
<div class="translation-container">
  <div class="api-key-section">
    <label for="api-key">DeepL API Key:</label>
    <input type="text" id="api-key">
  </div>

  <div class="language-selection">
    <select id="source-lang">
      <option value="JA">日本語</option>
      <option value="EN">英語</option>
      <option value="IT">イタリア語</option>
      <option value="FR">フランス語</option>
    </select>
    →
    <select id="target-lang">
      <option value="EN">英語</option>
      <option value="JA">日本語</option>
      <option value="IT">イタリア語</option>
      <option value="FR">フランス語</option>
    </select>
  </div>

  <textarea id="input-text" placeholder="ここにテキストを入力"></textarea>
  
  <div id="count">
    半角文字換算：0文字<br>
    全角文字換算：0文字<br>
    DeepL API Free 残り文字数：500,000文字
  </div>

  <button id="translate-button">翻訳</button>
  <div id="translation-result"></div>
  <div id="error-message"></div>
</div>
```

### 4. 主要な機能の実装

```javascript
// 文字数カウント機能
function countCharacters(text) {
  let fullWidthCount = 0;
  let halfWidthCount = 0;
  for (let char of text) {
    if (char.match(/[^\x00-\xff]/)) {  // 全角文字の判定
      fullWidthCount += 1;
      halfWidthCount += 2;
    } else {
      halfWidthCount += 1;
      fullWidthCount += 0.5;
    }
  }
  return { halfWidth: halfWidthCount, fullWidth: fullWidthCount };
}

// 翻訳機能
async function translateText() {
  // APIキーと入力テキストの取得
  const apiKey = document.getElementById('api-key').value.trim();
  const text = document.getElementById('input-text').value.trim();
  
  // バリデーション
  if (!apiKey || !text) return;

  try {
    const response = await fetch('/path/to/translation-proxy.php', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        text,
        source_lang: document.getElementById('source-lang').value,
        target_lang: document.getElementById('target-lang').value,
        api_key: apiKey
      })
    });

    const data = await response.json();
    if (data.translations) {
      showTranslation(data.translations[0].text);
    }
  } catch (error) {
    showError(error.message);
  }
}
```

## 実装のポイント

1. **文字数カウント**
   - 全角/半角を区別してカウント
   - リアルタイムでの更新
   - 月間制限（50万文字）との比較

2. **セキュリティ対策**
   - APIキーの保護（プロキシ経由）
   - XSS対策
   - 入力値のバリデーション

3. **UX向上**
   - エラーメッセージの表示
   - ローディング状態の表示
   - APIキーの保存（LocalStorage）

4. **保守性**
   - モジュール化されたコード
   - エラーハンドリング
   - コメントによる説明

## カスタマイズのヒント

1. **追加可能な機能**
   - 翻訳履歴の保存
   - 文字数制限のアラート
   - ショートカットキー
   - 翻訳結果のコピー機能

2. **スタイリング**
   - レスポンシブデザイン
   - ダークモード対応
   - アニメーション効果

## 注意点

- DeepL API Freeの利用制限（月間50万文字）を超えないよう注意
- APIキーは適切に管理（環境変数やconfig管理推奨）
- プロキシファイルのパーミッション設定に注意
- HTTPS環境での運用推奨

## まとめ

DeepL API Freeを使用することで、高品質な翻訳機能を無料で利用できます。文字数カウンターと組み合わせることで、制限を意識しながら効率的に翻訳作業を行えます。

また、このコードはGitHubで公開されている[DeepLの公式クライアントライブラリ](https://github.com/DeepLcom)と互換性があり、必要に応じて拡張することも可能です。

## 参考リンク

- [DeepL API Documentation](https://www.deepl.com/docs-api)
- [DeepL API Pricing](https://www.deepl.com/pro#developer)
- [DeepL API GitHub](https://github.com/DeepLcom)