Reactで画像がクロスフェードするスライドショーを作る

2025/08/19 (火) - 09:00 JavaScript

Reactで複数枚の画像をクロスフェードしてループで切り替えるスライドを簡易的に作ってみる。

スライドショーイメージ

JavaScriptのコードは以下の通り。予め画像を用意しておきそれを1枚ずつ切り替える想定です。ポイントとしては画像のクロスフェードを制御をCSS側で制御することで、class属性の有無で表示・非表示を切り替えます。

import React, { useState, useEffect } from 'react';
import './ImageSlider.css';
// 画像の枚数とファイル名を配列で指定
const images = [
 'slide1.webp',
 'slide2.webp',
 'slide3.webp'
];
function ImageSlider() {
 const [currentIndex, setCurrentIndex] = useState(0);
 useEffect(() => {
  // setIntervalで3秒ごとに画像を切り替えるタイマーを設定
  const timerId = setInterval(() => {
   setCurrentIndex(prevIndex => (prevIndex + 1) % images.length);
  }, 3000);
  return () => clearInterval(timerId);
 }, []);
 return (
  <div className="imgSlide">
   {images.map((image, index) => (
    <img
     key={index}
     src={image}
     alt={`画像No:${index + 1}`}
     className={`imgSlideItem ${currentIndex === index ? 'isActive' : ''}`}
     width={640}
     height={480}
    />
   ))}
  </div>
 );
}
export default ImageSlider;

CSSは以下の通りで画像の表示制御をします。transitionプロパティで透明度のスピードなどを制御します。標準では画像の透明度のopacityプロパティを0で不可視にしておき、アクティブになったらclass属性を付与し可視させます。

.imgSlide{
 position: relative;
 width: 640px;
 height: 480px;
 overflow: hidden;
}
.imgSlideItem{
 display: block;
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 object-fit: cover;
 opacity: 0; /* 非アクティブ時は画像の透明度を0 */
 transition: opacity 1s ease-in-out; /* 1秒かけてフェードイン・アウト */
}
.imgSlideItem.isActive{
 opacity: 1; /* アクティブ時は画像の透明度を1 */
}

コンポーネント化して任意の位置に埋め込むのがいいでしょう。

特定のHTML要素以外をクリックした時にイベントを発火

2025/08/13 (水) - 09:00 JavaScript

Webページで特定の要素をクリックした時と、それ以外の要素をクリックした時とで処理を分岐したい…。例えば親以上の要素にイベントハンドラを設定してしまうと、自身の要素もイベントハンドラの対象になってしまうのでそれを防ぎたい。

モーダルウィンドウなどで指定した要素以外をクリックしたらイベントを発火させたい場合は以下のように実装します。closestメソッドを使うと指定したセレクタ自身、または最も近い親要素を取得できます。targetと組み合わせて指定したセレクタが含まれなかったら〜で判別します。HTMLの例は以下の通り。

<div id="modal">モーダルウィンドウ</div>

JavaScript

document.addEventListener('click', function(e){
 if(!e.target.closest('#modal')) {
  console.log('モーダル以外をクリック!');
 } else {
  console.log('モーダルをクリック!');
 }
});

jQueryで似たようなことを再現する場合

$(document).on('click',function(e) {
 if(!$(e.target).closest('#modal').length) {
  console.log('モーダル以外をクリック!');
 } else {
  console.log('モーダルをクリック!');
 }
});

Reactで似たようなことを再現する場合

useRefを使うことでDOMを参照し、イベントハンドラを実行できるようにします。

import React, { useEffect, useRef } from 'react';
function App() {
 const modalRef = useRef<HTMLDivElement | null>(null);
 useEffect(() => {
  const handleClickOutside = (e: MouseEvent) => {
   if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
    console.log('モーダル以外をクリック!');
   } else {
    console.log('モーダルをクリック!');
   }
  };
  document.addEventListener('click', handleClickOutside);
 }, []);
 return (
  <div id="modal" ref={modalRef}>モーダルウィンドウ</div>
 );
}
export default App;

これを使うことで例えば、モーダルウィンドウやドロップダウンメニュー以外の外側をクリックしたら要素を非表示にする…など実現できます。

Movable Type(MT)で年ごと分割した月別アーカイブリストを実装

2025/08/01 (金) - 09:00 Program

Movable Type(MT)で以下のように年度ごとで見出しを分け、月ごとのアーカイブリストを作成する場合の実装方法。

Movable Type実装イメージ

環境は以下の通り。

  • Movable Type 8.x

実装するため、予め月別アーカイブを出力するように設定したうえでMovable Typeのデザインテンプレートの任意のモジュール等に以下のコードを入れて再構築する。

<MTSetVar name="current_year" value="0">
<MTArchiveList archive_type="Monthly" sort_order="descend">
 <MTSetVarBlock name="entry_year"><MTArchiveDate format="%Y"></MTSetVarBlock>
 <MTIf name="entry_year" ne="$current_year">
  <MTIf name="current_year" ne="0">
</ul>
  </MTIf>
<h2><MTArchiveDate format="%Y年"></h2>
<ul>
 <MTSetVar name="current_year" value="$entry_year">
 </MTIf>
 <li><a href="<MTArchiveLink>"><MTArchiveDate format="%Y年%m月"></a></li>
 <MTIf name="__last__">
</ul>
 </MTIf>
</MTArchiveList>

マークアップやデザインはサイトに合わせて調整すること。

Dockerを使いローカルでWordPress動かす(nginx/PHP/MySQL)

2025/07/18 (金) - 09:00 Server

DockerでLAMPを構築しWordPress 6.8を動かしたときのメモ。あらかじめDockerの環境をインストールしている前提で、環境は以下の通りです。

  • nginx 1.29
  • PHP 8.3
  • MySQL 8.0

ファイル構成は以下の通り作成します。

[作業ディレクトリ]
├── docker-compose.yml
├── app
│   └── WordPressのファイル群
├── nginx
│   └── default.conf
└── php
 └── Dockerfile

appディレクトリにはWordPressの第るを用意しておきます。MySQLの基本情報(データベース名、ユーザ名、パスワードなど)やブラウザでアクセスする際のnginxのポート番号を指定します。こちらは今回8080とし、http://localhost:8080でアクセスできるようにします。

Dockerの設定

docker-compose.ymlに以下の通り記述します。

version: '3.8'
services:
 # MySQL
 mysql:
 image: mysql:8.0
 container_name: wp_mysql
 environment:
  MYSQL_ROOT_PASSWORD: root_password #MySQLのrootパスワード
  MYSQL_DATABASE: wp_db #MySQLのDBパスワード
  MYSQL_USER: wp_user #MySQLのDBユーザ
  MYSQL_PASSWORD: wp_password #MySQLのパスワード
 volumes:
  - db_data:/var/lib/mysql
 restart: unless-stopped
 # PHP(PHP-FPM)
 php:
 build: ./php
 container_name: wp_php
 volumes:
  - ./app:/var/www/html
 expose:
  - 9000
 restart: unless-stopped
 # Nginx
 nginx:
 image: nginx:latest
 container_name: wp_nginx
 volumes:
  - ./app:/var/www/html
  - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
 ## ブラウザでアクセスする際のポート番号
 ports:
  - "8080:80"
 depends_on:
  - php
 restart: unless-stopped
volumes:
 db_data:

nginxの設定

nginx/default.confでWebサーバーの設定をします。ディレクトリの設定やphp-fpmとの連携などを行います。

server {
 listen 80;
 server_name localhost;
 root /var/www/html;
 index index.php index.html index.htm;
 location / {
  try_files $uri $uri/ /index.php?$args;
 }
 location ~ \.php$ {
  # PHPコンテナ(docker-compose.ymlのサービス名)とポートを合わせる
  fastcgi_pass php:9000;
  fastcgi_index index.php;
  include fastcgi_params;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  fastcgi_param PATH_INFO $fastcgi_path_info;
 }
 location ~ /\. {
  deny all;
 }
}

PHPの設定

php/Dockerfileに以下を記述します。WordPressの動作に必要なモジュールや実行ユーザ名を設定します。

FROM php:8.3-fpm
# WordPressに必要なPHP拡張モジュール
RUN apt-get update && apt-get install -y \
 libfreetype6-dev \
 libjpeg62-turbo-dev \
 libpng-dev \
 libzip-dev \
 libonig-dev \
 && docker-php-ext-configure gd --with-freetype --with-jpeg \
 && docker-php-ext-install -j$(nproc) gd mysqli pdo_mysql zip mbstring opcache
WORKDIR /var/www/html
USER www-data

Dockerの起動

すべての作業が終わったら、作業ディレクトリに移動しDockerのコンテナを実行します。

$ cd 作業ディレクトリ(置き換えてね)
$ docker-compose up -d

正常に起動したらhttp://localhost:8080にアクセスするとインストールが起動するので、通常通りインストールします。MySQLの設定は冒頭のdocker-compose.ymlの設定を割り当てます。

WordPress動作イメージ

起動した実行例。

Dockerの停止

Dockerを停止する場合は以下のコマンドを実行します。

$ docker-compose down

WordPressの投稿内にウィジェットを配置する

2025/07/14 (月) - 09:00 Program

WordPressの投稿や固定ページの本文内に任意のウィジェットを挿入したい場合。まず、あらかじめにWordPressのfunction.phpにウィジェットの作成をし、WordPress管理画面の[外観]→[ウィジェット]からウィジェットの設定しておきます。

if(function_exists('register_sidebar')) {
 register_sidebar(array(
  'name' => 'ウィジェットのタイトル' ,
  'id' => 'ウィジェットのID', //半角英数小文字
  'description' => 'ウィジェットの説明文',
  'before_widget' => '<aside>',
  'after_widget'  => '</aside>',
  'before_title'  => '<h2>',
  'after_title'   => '</h2>'
 ));
}

さらにfunction.phpにショートコードでウィジェットを呼び出す関数を作成します。

function widget_post_func($atts) {
 $atts = shortcode_atts(array(
  'id' => '',
 ), $atts, 'widget');
 if (empty($atts['id'])) {
  return false;
 }
 ob_start();
 dynamic_sidebar($atts['id']);
 $widget_content = ob_get_clean();
 return $widget_content;
}
add_shortcode('widget', 'widget_post_func');

最後にWordPressの投稿画面の任意の位置にショートコードを埋め込みます。idの属性には設定したウィジェットのIDを入れておきます。

[widget id="ウィジェットのID"]

プレビューすると表示されると思います。ウィジェットを更新するとページや記事に埋め込んだすべてのウィジェットが一括更新されるので便利です。

ウィジェットを表示した例

例えば広告バナーやリンク集、CTAなどを設置したい場合に有効です。

OpenAI APIとNext.js(React)でGPTの回答をマークダウン(Markdown)で取得する

2025/06/02 (月) - 09:00 JavaScript

Next.js(AppRouter)とOpenAI APIを利用して、テキストフォームから入力した質問の回答をマークダウン形式で取得し、HTMLで表示するサンプル。Next.jsやReactでマークダウンを扱う場合はreact-markdownのモジュールを使うと便利です。

$ npm i react-markdown

予めモジュールを入れておきましょう。あとはOpenAI PlatformでAPIキーの発行とクレジットの確保も忘れずに。今回は以下の環境下で実装しています。

  • Next.js 14.2.x (AppRouter)
  • React 18.3.x
  • react-markdown 1.9.x

取得したAPIキーは.envで設定します。

API_KEY=sk-********************

フロント部分のpage.tsxで質問フォームと表示エリアを実装。ポイントは回答部分をReactMarkdownでマークアップすることです。これによりマークダウン形式のテキストがHTMLに変換されて表示されます。

'use client';
import { useState } from 'react';
import ReactMarkdown from 'react-markdown';
export default function Home() {
 const [prompt, setPrompt] = useState('');
 const [response, setResponse] = useState('');
 const [loading, setLoading] = useState(false);
 const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  setLoading(true);
  const res = await fetch('/api/chatgpt', {
   method: "POST",
   headers: {
    'Content-Type': 'application/json',
   },
   body: JSON.stringify({ prompt })
  });
  const data = await res.json();
  setResponse(data.text);
  setLoading(false);
 };
 return (
  <main>
   <form onSubmit={handleSubmit}>
    <textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="質問を入力してください"></textarea>
    <button type="submit" disabled={loading}>質問する</button>
   </form>
   {loading && <p>生成中…</p>}
   {response && (
    <div>
     <ReactMarkdown>{response}</ReactMarkdown>
    </div>
   )}
  </main>
 );
}

API部分の実装。Next.jsのAPI Routesを利用して実装しました。srcディレクトリに/api/chatgpt/route.tsを配置し実装します。フロントからリクエストされたされたプロンプトをPOSTで処理します。

import { NextResponse } from 'next/server';
export async function POST(req: Request) {
 try {
  const { prompt } = await req.json();
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
   method: 'POST',
   headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.API_KEY}`,
   },
   body: JSON.stringify({
    model: 'gpt-4o-mini', //モデルを選択
    messages: [
     { role: 'user', content: prompt }
    ],
   }),
  });
  const data = await response.json();
  const text = data.choices[0].message?.content?.trim() || '';
  return NextResponse.json({ text });
 } catch (error) {
  console.error(API エラー:', error);
  return NextResponse.json({ error: '生成に失敗しました' }, { status: 500 });
 }
}

実際に実装し質問を実行した際の表示例。蔦屋重三郎の生い立ちについて質問し正常にHTMLフォーマットで回答文が表示されています。

回答結果

フロントのCSS側はマークダウンで再現できる一般的なHTML要素(見出し、リスト、table等)が綺麗に見やすく表示されるように整えておく必要があります。

AWS LambdaとDeepSeek APIでLINE botを作る

2025/05/16 (金) - 09:00 Others

LINEのMessaging API&AWS Lambda&DeepSeek APIを組み合わせて自動返信するLINE Botを作成しました。構成は以下とします。LINE Messaging APIからAWS LambdaからDeepSeek APIを通しユーザーと会話するフローです。

インフラ構成図

LINEのbotを作成するには以下のものが必要です。また、あらかじめDeepSeek PlatformのAPIキーを用意しておきます。

  • LINE Developers
  • LINE 公式アカウント

LINE Developersの設定

まず、LINE Developersにアクセスし、ログインし開発者アカウントを設定します。LINEアカウントのチャットボットの肝となる「プロバイダー」を作成します。プロバイダーとはLINE開発ツールでサービスを提供する個人・組織のことです。

続いてMessaging APIチャネルの作成します。

LINE公式アカウントの作成

LINE公式アカウントの作成にアクセスし、アカウントを作成します。

チャネル基本設定

  • [基本設定]でBOTの名前やアイコンなどを設定します。
  • [機能の利用]でグループ・複数人トークへの参加を有無、写真や動画の受け取りの有無を設定します。[写真や動画の受け取り]は基本OFFが良いでしょう。

Messaging APIの設定

Messaging APIの設定はLINE Developersのコンソールから設定します。作ったチャンネルのアイコンをクリックし、[Messaging API設定]の[Webhook設定]からWebhook URLとWebhookの利用の有無を設定。

  • [応答機能]のチャットを無効にする
  • [Webhook]を有効にし、Messaging APIの設定を開く(後述)
  • [応答機能]の応答メッセージを無効にする
  • ページ下部にある[チャネルアクセストークン(長期)]を発行し控えておきます

AWS Lambdaの設定

AWS Lambdaにアクセスし新規で関数を作成します。[関数名]は任意の名前に設定し、[ランタイム]は[Python 3.x]を選択します。[アーキテクチャ]は[x86_64]のままにし[関数を作成]を実行します。

次に関数の設定に移動し、[環境変数]であらかじめ控えておいたDeepSeekのAPIキーと、LINEのチャネルアクセストークンを設定しておきます。今回はそれぞれDEEPSEEK_APIKEYLINE_ACCESS_TOKENという名前でキーを設定しました。

AWS Lambdaの環境変数設定

次に[コード]に移動し、Python 3.xとしてコードを実装します。コードの例は下記の通り。またPythonでAPI通信をするためにはrequestsモジュールが必要なのですが、Lambdaでは使えないので適宜レイヤーで追加しておきます。

コードの例

import json
import os
import requests
LINE_ACCESS_TOKEN = os.getenv('LINE_ACCESS_TOKEN')
DEEPSEEK_API_KEY = os.getenv('DEEPSEEK_APIKEY')
LINE_REPLY_API_URL = 'https://api.line.me/v2/bot/message/reply'
DEEPSEEK_API_URL = 'https://api.deepseek.com/chat/completions'
def lambda_handler(event, context):
 body = json.loads(event['body'])
 for message_event in body.get('events', []):
  if message_event['type'] != 'message' or message_event['message']['type'] != 'text':
   continue
  user_message = message_event['message']['text']
  reply_token = message_event['replyToken']
  gpt_reply = ask_gpt(user_message)
  reply_to_line(reply_token, gpt_reply)
 return {
  "statusCode": 200,
  "body": json.dumps('OK')
 }
def ask_gpt(user_input):
 headers = {
  "Content-Type": "application/json",
  "Authorization": f"Bearer {DEEPSEEK_API_KEY}"
 }
 data = {
  "model": "deepseek-chat",
  "messages": [
   {"role": "system", "content": """* 日本語で返答してください
* あなたはユーザーのアシスタントです
* ユーザーのことを「センパイ」と呼称します
* ユーザーを慕う後輩のようなキャラクターです
* ユーザーに好意を持ち敬語で話します"""},
   {"role": "user", "content": user_input}
  ]
 }
 response = requests.post(DEEPSEEK_API_URL, headers=headers, json=data)
 response_data = response.json()
 if 'choices' in response_data:
  return response_data['choices'][0]['message']['content']
 else:
  return '現在応答できません'
def reply_to_line(reply_token, message_text):
 headers = {
  "Content-Type": "application/json",
  "Authorization": f"Bearer {LINE_ACCESS_TOKEN}"
 }
 data = {
  "replyToken": reply_token,
  "messages": [
   {
    "type": "text",
    "text": message_text
   }
  ]
 }
 requests.post(LINE_REPLY_API_URL, headers=headers, json=data)

コードを保存してデプロイしたら、Lambdaの[関数の概要]から[関数 URL]をコピーしておきます。

Messaging APIの設定

再びLINE DevelopersのコンソールからMessaging APIの設定にアクセスし、[Webhook URL]に先程作ったLambdaの[関数 URL]のエンドポイントを入力し[更新]を押下します。その後[検証]を押下し正常にレスポンス(200)が返ってくることを確認します。

Webhook URLの設定

同じ設定画面にあるQRコードをスマホで読み込み、LINEの友達に追加します。レッツトーク!

LINEトークのサンプル

にゃるぴにも優秀な後輩アシスタントができました。今回は安価なDeepSeek APIで実装しましたがもちろんGPTのOpenAIのAPIでも構いません。

Messaging APIのプランと値段

Messaging APIはプランによって送信料が変わります。今回は自分用なので無料のコミュニケーションプランで作成しました。Messaging APIの料金は以下ページに掲載されています。

さくらのレンタルサーバーのphpMyAdminで巨大なSQLファイルをインポートできない時

2025/05/09 (金) - 09:00 Server

さくらのレンタルサーバーのスタンダードプラン以上では標準でMySQLが搭載されており、簡単にデータベースを扱えます。それに伴いデータベース管理ツールのphpMyAdminも装備されておりGUIでデータベース操作が行え、外部からSQLのファイルをインポートできます。しかしさくらのレンサバ標準のphpMyAdminでインポートできるファイルの上限は32MBまでのようです。

数百MBあるような巨大なSQLファイルを読み込ませることができません。SQLファイルをdata.sql.zipのようなファイルの命名規則でzipで圧縮し、32MB以下まで落としてアップもできますが、展開とインポートに時間がかかってタイムアウトすることがあります。そこでどうするか?考えました。

phpMyAdminのインポート画面

自前でphpMyAdminをインストールする

借りているサーバーに自分でphpMyAdminをインストールし、尚且つphp.iniなどでupload_max_filesizeの値を256MBなどに引き上げて、SQLファイルをアップロードできるようにします。

インポートには時間がかかるので、set_time_limitで実行時間を引き上げたりmemory_limitでメモリの上限を上げる必要もあります。ただしあくまで共用のレンタルサーバのためあまり高負荷をかけ続けるとアカウントの利用制限などに抵触する可能性もあるため注意が必要です。

SSHでサーバに接続してSQLファイルをインポートする

一部サーバーコマンドやSQLコマンドの知識が必要ですがこれが一番確実です。予めインポートしたいSQLファイルをレンタルサーバの任意の場所にアップロードしてきます。次にさくらのレンタルサーバにSSHで接続します。ログインパスワードはFTPと同じパスワードです。

ssh ユーザー名@ユーザー名.sakura.ne.jp

ログインしたらMySQLにログインします。予めさくらのレンタルサーバのコントロールパネルよりデータベースとユーザ名を作成しておきます。レンタルサーバーのコントロールパネルからデータベースの収容サーバ名(mysql****.db.sakura.ne.jp)も控えておきます。パスワードはデータベースを発行したときに設定したデータベースのパスワードです。

mysql -u データベースユーザー名 -h mysql****.db.sakura.ne.jp -p

ログインしたらデータベースを選択します。show databasesでデータベースがあることを確認し、useコマンドで使用するデータベースを選択します。

show databases;
〜データベース一覧が表示される〜
use 使用するデータベース名;

先ほどアップロードしたSQLファイルをインポートします。データベースの構造にもよりますが、数万レコードあるSQLでも1分もかからず終わるはずです。

source /home/ユーザー名/www/data.sql

インポートが終わると入力待ち状態になるので、MySQLを終了します。

exit;

さきほどレンタルサーバにアップロードしたSQLファイルは不要なので削除しておきます。

rm /home/ユーザー名/www/data.sql

さらにサーバからログアウトします。

exit

この方法のがおそらくインポートを失敗せずに行けると思います。

AWS CloudFrontで特定ファイルのみキャッシュを無効にする

2025/05/02 (金) - 09:00 Server

CloudFrontでウェブサイトを配信するとき、特定のファイルのみのキャッシュを無効化したいときの設定方法。例えば頻繁に内容が変わるHTMLファイルはキャッシュせず、更新頻度が少なかったり重いデータの画像やCSS・JavaScriptなどはCloudFrontでキャッシュさせたいときなど。

AWS CloudFrontの設定

今回は例として、HTMLファイルだけキャッシュの除外をする設定とします。

AWS CloudFrontから該当のディストリビューションを選択し、[ビヘイビア]に移動。[ビヘイビアを作成]を選択し新規作成します。[パスパターン]に*.htmlと入力。[オリジンとオリジングループ]でオリジンを選択してから、他のオプションは以下のように設定。

CloudFront Behavior

* オブジェクトを自動的に圧縮:Yes
* ビューワープロトコルポリシー:Redirect HTTP to HTTPS
* 許可された HTTP メソッド:GET, HEAD
* ビューワーのアクセスを制限する:No
* キャッシュキーとオリジンリクエスト:Legacy cache settings
* ヘッダー:なし
* クエリ文字列:すべて
* cookie:すべて
* オブジェクトキャッシュ:Customize
* 最小 TTL:0
* 最大 TTL:0
* デフォルト TTL:0

すべてのTTLを0にすることで、キャッシュされずに読み込まれます。上記を設定したら保存します。

設定の反映の確認方法

Webブラウザのネットワークヘッダーで確認できます。ChromeのWeb開発者ツールであれば[Network]からファイルを選択しレスポンスヘッダーのX-Cacheから確認できます。

X-Cache:Hit from cloudfront

CloudFrontのキャッシュになっている場合は、X-CacheHit from cloudfrontになります。今回の場合CSSや画像ファイルはキャッシュされます。

X-Cache:Miss from cloudfront

一方キャッシュされていない場合はX-CacheMiss from cloudfrontの値になります。これはキャッシュされずにオリジンから呼び出しているものになります。

注意点としては、該当のファイルはキャッシュされないためオリジンに負荷がかかるということです。S3やEC2から呼び出す場合は転送量やプロセッサの振る舞いに注意しましょう。

Claude APIでWordPressの記事を翻訳する

2025/04/28 (月) - 09:00 Program

今回はWordPressの記事タイトルと本文を生成AIを組み合わせて自動的に機械翻訳する機能を簡易的に実装しました。記事詳細ページに「英訳を見る」ボタンを配置し、押下したら英訳ページに遷移しClaude APIを利用し翻訳します。

ブラウザの機能やGoogleなどの翻訳サービスで簡単かつ無料で全文翻訳出来るので不要と言えば不要ですがネタ的に。

実装ポイントとしては以下。

  • 翻訳に時間とAPI使用料金が取られるので初回のみ翻訳
  • 2回目以降は翻訳内容をキャッシュして表示
  • 翻訳ページへの切り替えはクエリパラメータで判断
  • アクセス解析の時にパラメータの有無で翻訳ページのアクセス数を判断できる

翻訳前のブログ記事

翻訳前のブログ記事の表示(日本語)。デメリットは以下の通り

  • 翻訳内容の精度は元の文章の品質とAIモデルに依存する
  • あまり長い文章は非対応(max-tokenを超えると文章が途中で切れる)
  • APIの使用利用料金に注意

あらかじめAnthropic ConsoleでAPIキーの取得と課金は済ませておきましょう。

翻訳後のブログ記事

翻訳後のブログ記事の表示(英語)。環境は以下の通り

  • WordPress 6.5
  • PHP 8.2

細かいエラーやセキュリティ処理はしていないので、参考までに。また一部マークアップが崩れる可能性もあるのでその点も注意しましょう。

コード例

function.phpに以下を記述。指定したクエリパラメータの記事詳細にアクセスすると初回翻訳が実行されます。1度翻訳した内容はカスタムフィールドに格納するため、2回以降はカスタムフィールドの翻訳内容を表示します。

function translationContent(){
 $API = 'https://api.anthropic.com/v1/messages';
 $API_KEY = '取得したAPIキーを設定';
 if( get_post_meta(get_the_ID(), 'translation_en') ) {
  //翻訳後ならキャッシュを表示
  $the_content = get_post_meta(get_the_ID(),'translation_en',true);
  return $the_content;
 }
 //翻訳前なら翻訳を実行
 $system = '指定したテキストを英語で翻訳する。HTMLタグは変えずにテキスト内容だけ翻訳する。';
 $single_title = get_the_title();
 $single_date = get_the_time('Y/m/d (D) - H:i');
 $single_content = get_the_content();
 $content = "<h1>{$single_title}</h1><p><time>{$single_date}</time></p>{$single_content}";
 $data = json_encode([
  'model' => 'claude-3-5-haiku-20241022', //モデルを選択
  'max_tokens' => 8192, //最大トークン(モデルに依存)
  'system' => $system,
  'messages' => [
   ['role' => 'user', 'content' => $content]
  ]
 ]);
 $ch = curl_init($API);
 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
 curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'Content-Type: application/json',
  'anthropic-version: 2023-06-01',
  'x-api-key: '.$API_KEY
 ]);  
 $result = curl_exec($ch);
 $httpcode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
 curl_close($ch);
 $output = json_decode($result, true);
 if($httpcode===200){
  $contents = $output['content'][0]['text'];
  update_post_meta(get_the_ID(), 'translation_en', $contents );
  return $contents;
 }else{
  return 'エラー: '.$result;
 }
}

single.phpの任意の位置に以下を記述。クエリパラメータの有無で本来の日本語の記事と、翻訳の記事を分岐表示します。このとき英訳のページへの同線を設置します。

<?php
if($_GET['lang']==='en'){
 echo translationContent();
}else{
?>
 <h1><?php the_title(); ?></h1>
 <p><time><?php the_time('Y/m/d (D) - H:i') ?></time></p>
 <p><a href="<?php the_permalink() ?>?lang=en">この記事を英語で見る(English)</a></p>
<?php
 the_content();
}
?>

もし翻訳内容を変えたい場合はカスタムフィールドを直接変更します。また翻訳内容をリセットして再翻訳したい場合はカスタムフィールドを削除して保存した後、再度翻訳をします。

本格的に翻訳記事を用意したり、機密情報を扱うのであれば専門の翻訳業者に依頼し、WordPressの多言語プラグインを利用するのが最適です。しかし生成AIの翻訳精度もかなりの物ですね。

ページの先頭へ