1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

サーバーなしで無料ツールサイトを14個、ほぼ全部5言語対応で作った構成メモ

1
Posted at

はじめに

Tooleek は、登録なしで使える無料Webツールを集めたサイト群です。PDF結合、画像圧縮、QRコード、タイマー、タイピング、ナンプレなどがあります。

この記事では、ハブ+ツール14個の計15サイトを、サーバーを持たず静的HTMLだけで動かしている構成と、多言語化(韓国語・英語・日本語・中国語簡体字・スペイン語)の仕組みをまとめます。アクセス数などの数字の話はなく、構成だけの話です。

全体構成

  • ハブ tooleek.com + ツール14個(サブドメイン。例: pagefold.tooleek.com)
  • すべて Cloudflare Pages の静的サイト。サーバーコードもDBもありません
  • 各サイトに build.mjs が1つあり、実行すると dist/ に素のHTMLを書き出します
  • 共通部品は core/ にまとめ、各サイトのフォルダにコピーして使います
ad-tool-sites/
  core/          # layout.mjs, base.css, common.js, network.mjs, words.mjs
  pagefold/      # build.mjs, i18n.txt, assets/, dist/
  imglite/       # build.mjs, i18n.txt, i18n-long.txt, assets/, dist/
  ...(ツールごとに同じ形)

core/ の中身はこうなっています。

ファイル 役割
layout.mjs ページの外枠、SEOタグ、hreflang、サイトマップ、OG画像、翻訳エンジン
base.css / common.js 共通CSSと、ブラウザ側の小さなヘルパー(翻訳、localStorage、乱数など)
network.mjs フッターに出す姉妹サイト一覧(名前と説明を5言語の配列で持つ)

core/ を各フォルダにコピーしているので、どのサイトもフォルダ単体でビルドできます。

ビルド: build.mjs はHTMLを書くだけ

PDFツール(pagefold)の build.mjs から、翻訳まわりを抜き出すとこうなります。

import { render, write, sitemap, i18n, i18nReport, i18nClient, loadDict /* ほか */ } from './core/layout.mjs';
const OUT = 'dist';
const DICT = loadDict();   // i18n.txt を読む
const TR = i18n(DICT);     // 言語ごとの翻訳関数を作る
// ...ページを組み立てて書き出す...
sitemap(OUT, site.base, pages);
i18nClient(DICT, OUT + '/assets'); // ブラウザ用辞書 i18n.ja.js などを出力
i18nReport();                      // 未翻訳の文を i18n-missing.json に書き出す

ライブラリはバンドラーを通さず、node_modules から dist/ にそのままコピーしています。

fs.copyFileSync('node_modules/pdf-lib/dist/pdf-lib.min.js', VEN + '/pdf-lib.min.js');
for (const f of ['pdf.min', 'pdf.worker.min'])
  fs.copyFileSync(`node_modules/pdfjs-dist/legacy/build/${f}.mjs`, `${VEN}/pdfjs/${f}.js`);

多言語化: 英語の文そのものをキーにする

URL

  • 韓国語がデフォルトで /
  • それ以外は /en/ /ja/ /zh/ /es/ の下に同じパスで生成
  • 全ページに hreflang(と x-default)を入れ、sitemap.xml にも xhtml:link で各言語版を書きます

例外として、給与計算のサイトは韓国の税制専用なので韓国語のみです。そのため「ほぼ全部5言語」です。

コード側: 韓国語と英語を並べて書く

ページのコードでは、韓国語と英語を並べて書きます。

T('다른 PDF 도구', 'More PDF tools')

日本語・中国語・スペイン語は、この英語の文をキーにして別ファイルから引きます。キーを別に考えなくていいのが利点です。

i18n.txt: 4行で1エントリ

@Merge PDF
PDF結合
合并 PDF
Unir PDF

@英語 の次に日本語、中国語、スペイン語が1行ずつ続きます。改行を含む文は \n と書きます。読み込みはこれだけです。

export function loadDict(file = 'i18n.txt') {
  let src;
  try { src = fs.readFileSync(file, 'utf8'); } catch { return {}; }
  const lines = src.replace(/\r/g, '').split('\n'), d = {}, dec = (x) => (x || '').replace(/\\n/g, '\n');
  for (let i = 0; i < lines.length; i++) if (lines[i].startsWith('@')) {
    d[dec(lines[i].slice(1))] = [dec(lines[i + 1]), dec(lines[i + 2]), dec(lines[i + 3])];
    i += 3;
  }
  return d;
}

長い本文(説明文やFAQ)は同じ形式の i18n-long.txt に分けています。

訳がない文はビルド時にファイルへ出す

const MISSING = new Map();
export function i18n(dict) {
  const IDX = { ja: 0, zh: 1, es: 2 };
  return (L) => {
    const t = (ko, en) => {
      if (L === 'ko') return ko;
      if (L === 'en') return en;
      const row = dict[en];
      if (row && row[IDX[L]] != null && row[IDX[L]] !== '') return row[IDX[L]];
      MISSING.set(en, true); // 訳がない
      return en;             // とりあえず英語で出す
    };
    return { t /* ほか */ };
  };
}

訳がない文は英語のまま出力し、ビルドの最後に i18n-missing.json へ書き出します。訳が揃っていればこのファイルは消えます。英語の原文を少し直すと、その文の訳が「未翻訳」としてここに出てくるので、直し漏れに気づけます。

ブラウザ側も同じキーで引く

ボタンの文言など、JavaScriptで出す短い文は、ビルド時に言語別の辞書 i18n.ja.js などを書き出しておき(400文字未満の文だけ)、ページで読み込みます。

// core/common.js
const D = window.I18N || {};
U.t = (ko, en) => (U.lang === 'ko' ? ko : U.lang === 'en' ? en : D[en] != null ? D[en] : en);

韓国語と英語のページでは辞書ファイルを読み込みません。

日本語ならではの調整

日本語UIでは、パズルの名前を「数独」ではなく「ナンプレ」と表記しています。

処理はすべてブラウザの中

  • PDF: pdf-lib と pdf.js
  • 画像: canvas
  • 設定や結果: localStorage(try/catch で包んだ小さなラッパー経由)

ファイルをサーバーへ送らないので、アップロード先のサーバーも保存先のDBもいりません。

OG画像もビルド時に作る

OG画像(1200×630)は、SVGを組み立てて @resvg/resvg-js でPNGにしています。フォントは Pretendard です。外部の画像生成サービスは使っていません。

デプロイ

Cloudflare Pages に、サイトごとのプロジェクト(tooleek-<フォルダ名>)としてアップロードしています。シェルスクリプト1本で、各サイトのビルドとアップロードを順番に行います。アップロード後は、本番のHTMLとローカルのビルド結果を比べるスクリプトで確認しています。

まだできていないこと

  • 日本語・中国語・スペイン語の訳は、まだネイティブのチェックを受けていません。不自然なところがあれば教えてください。
  • 反応速度・タイピングなどのテストでは、他の利用者と比べる順位やパーセンタイルを、根拠になるデータがないのであえて入れていません。

※この記事はAI(Claude)の支援を受けて書き、内容は実際のリポジトリのコードと照らし合わせて確認しました。

サイト: https://tooleek.com/ja/

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?