feat(site): add Japanese as a first-class marketing locale

Route /ja/ through extensible site locale prefixes, keep unmatched browsers on English, and leave blog bodies in English with Japanese chrome.
This commit is contained in:
tianma-if committed 2026-09-14 11:52:12 +08:00
1 parent 54edca629a
commit cf0df683ef
23 files changed
+1074 -73

No files matched your search

+18 -1
View File
@@ -13,7 +13,21 @@ export default defineConfig({
port: 4321,
host: "127.0.0.1"
},
integrations: [mdx(), sitemap(), icon(), react()],
integrations: [
mdx(),
sitemap({
i18n: {
defaultLocale: "zh-CN",
locales: {
"zh-CN": "zh-CN",
en: "en-US",
ja: "ja",
},
},
}),
icon(),
react(),
],
redirects: {
"/guides/evernote-migration": "/blog/evernote-migration-guide",
"/guides/memos-migration": "/blog/memos-migration-guide",
@@ -21,6 +35,9 @@ export default defineConfig({
"/en/guides/evernote-migration": "/en/blog/evernote-migration-guide",
"/en/guides/memos-migration": "/en/blog/memos-migration-guide",
"/en/guides/notion-migration": "/en/blog/notion-migration-guide",
"/ja/guides/evernote-migration": "/en/blog/evernote-migration-guide",
"/ja/guides/memos-migration": "/en/blog/memos-migration-guide",
"/ja/guides/notion-migration": "/en/blog/notion-migration-guide",
},
vite: {
plugins: [tailwindcss()],
@@ -1,9 +1,10 @@
---
import { Icon } from "astro-icon/components";
import { deploymentPrompts, manualDeploymentCopy } from "@/deployment-prompts";
import { getLocalizedPath } from "@/i18n";
const { locale = "zh-CN" } = Astro.props;
const isEnglish = locale === "en-US";
const isEnglish = locale !== "zh-CN";
const copy = isEnglish
? {
@@ -24,7 +25,7 @@ const copy = isEnglish
dockerTitle: "One-command Docker self-hosting",
dockerPrerequisite: "Install Docker with Docker Compose v2 before running the command.",
dockerGuide: "Open the complete Docker deployment guide",
dockerGuideHref: "/en/docker-deploy",
dockerGuideHref: getLocalizedPath(locale, "/docker-deploy"),
dockerCommandLabel: "COMMAND",
copyCommand: "Copy command",
promptLabel: "PROMPT",
@@ -3,7 +3,7 @@ import { getSiteLocale } from "@/i18n";
import { Icon } from "astro-icon/components";
const locale = getSiteLocale(Astro.url.pathname);
const isEnglish = locale === "en-US";
const isEnglish = locale !== "zh-CN";
---
<aside
+8 -2
View File
@@ -16,10 +16,16 @@ const copy = siteCopy[locale].nav;
href="https://www.dujin.org/"
target="_blank"
rel="noopener noreferrer"
aria-label={locale === "en-US" ? "Thanks to independent blogger Dujin for product feedback and tutorials" : "感谢独立博主缙哥哥在项目成长中提出的宝贵建议与教程分享"}
aria-label={
locale === "zh-CN"
? "感谢独立博主缙哥哥在项目成长中提出的宝贵建议与教程分享"
: locale === "ja"
? "独立ブロガー Dujin さんへの謝辞。製品への助言とチュートリアルに感謝します"
: "Thanks to independent blogger Dujin for product feedback and tutorials"
}
class="inline-flex min-h-11 sm:min-h-0 items-center hover:text-emerald-700 transition-colors"
>
{locale === "en-US" ? "Thanks" : "特别致谢"}
{locale === "zh-CN" ? "特别致谢" : locale === "ja" ? "謝辞" : "Thanks"}
</a>
<span class="text-slate-300">•</span>
<a href={getLocalizedPath(locale, "/self-hosted-evernote-alternative")} class="inline-flex min-h-11 sm:min-h-0 items-center hover:text-emerald-700 transition-colors">
+7 -4
View File
@@ -89,7 +89,7 @@ const copy = siteCopy[locale].hero;
<div class="animate-rise mt-6 w-full max-w-full z-30 relative" style="animation-delay: 240ms;">
<div class="pt-4 border-t border-slate-200/60 flex flex-wrap items-center gap-2">
<span class="text-[11px] font-semibold text-slate-500 mr-1 select-none">
{locale === "en-US" ? "Get Apps:" : "获取客户端:"}
{copy.getApps}
</span>
<a
href="https://github.com/tianma-if/edgeever/releases"
@@ -119,7 +119,7 @@ const copy = siteCopy[locale].hero;
<span>{copy.linux}</span>
</a>
<a
href="https://apps.apple.com/us/app/edgeever/id6792625631"
href={locale === "ja" ? "https://apps.apple.com/jp/app/edgeever/id6792625631" : "https://apps.apple.com/us/app/edgeever/id6792625631"}
target="_blank"
rel="noopener"
class="inline-flex min-h-11 sm:min-h-0 items-center gap-1.5 px-2.5 py-1 rounded-lg bg-slate-100/90 hover:bg-emerald-50 hover:text-emerald-700 hover:border-emerald-300 border border-slate-200/80 text-[11px] font-medium text-slate-700 transition-all shadow-2xs"
@@ -128,7 +128,7 @@ const copy = siteCopy[locale].hero;
<span>iOS</span>
</a>
<a
href="https://play.google.com/store/apps/details?id=org.edgeever.mobile"
href={locale === "ja" ? "https://play.google.com/store/apps/details?id=org.edgeever.mobile&hl=ja" : "https://play.google.com/store/apps/details?id=org.edgeever.mobile"}
target="_blank"
rel="noopener"
class="inline-flex min-h-11 sm:min-h-0 items-center gap-1.5 px-2.5 py-1 rounded-lg bg-slate-100/90 hover:bg-emerald-50 hover:text-emerald-700 hover:border-emerald-300 border border-slate-200/80 text-[11px] font-medium text-slate-700 transition-all shadow-2xs"
@@ -143,7 +143,7 @@ const copy = siteCopy[locale].hero;
class="inline-flex min-h-11 sm:min-h-0 items-center gap-1.5 px-2.5 py-1 rounded-lg bg-slate-100/90 hover:bg-emerald-50 hover:text-emerald-700 hover:border-emerald-300 border border-slate-200/80 text-[11px] font-medium text-slate-700 transition-all shadow-2xs"
>
<Icon name="bx:bx-extension" class="w-3.5 h-3.5" />
<span>{locale === "en-US" ? "Clipper" : "剪藏插件"}</span>
<span>{copy.clipper}</span>
</a>
</div>
</div>
@@ -193,6 +193,9 @@ const copy = siteCopy[locale].hero;
font-family:
"Inter Variable",
Inter,
"Hiragino Sans",
"Hiragino Kaku Gothic ProN",
"Yu Gothic",
"PingFang SC",
"Hiragino Sans GB",
"Microsoft YaHei",
+8 -4
View File
@@ -2,11 +2,15 @@
// @ts-ignore
import { Icon } from "astro-icon/components";
import { Marquee } from "@/components/magicui/marquee";
import { getSiteLocale } from "@/i18n";
const isEnglish = Astro.url.pathname === "/en" || Astro.url.pathname.startsWith("/en/");
const heading = isEnglish
? "Built with a modern, efficient technology stack"
: "基于现代且高效的技术栈构建";
const locale = getSiteLocale(Astro.url.pathname);
const heading =
locale === "zh-CN"
? "基于现代且高效的技术栈构建"
: locale === "ja"
? "モダンで効率的な技術スタックで構築"
: "Built with a modern, efficient technology stack";
const technologies = [
{ name: "React", icon: "simple-icons:react", hoverClass: "group-hover:text-indigo-600" },
@@ -1,21 +1,14 @@
---
import { getLocalizedPath, getSiteLocale, siteCopy, siteLocaleDataAttribute } from "@/i18n";
import { getLocalizedPath, getSiteLocale, siteCopy, siteLocaleDataAttribute, siteLocaleLabels, siteLocales } from "@/i18n";
const { pathname, compact = false } = Astro.props;
const locale = getSiteLocale(pathname);
const copy = siteCopy[locale].nav;
const languageOptions = [
{
locale: "zh-CN",
label: "简体中文",
path: getLocalizedPath("zh-CN", pathname),
},
{
locale: "en-US",
label: "English",
path: getLocalizedPath("en-US", pathname),
},
];
const languageOptions = siteLocales.map((item) => ({
locale: item,
label: siteLocaleLabels[item],
path: getLocalizedPath(item, pathname),
}));
const currentLanguageLabel = languageOptions.find((item) => item.locale === locale)?.label ?? copy.language;
---
@@ -187,4 +187,98 @@ export const alternativePageCopy = {
},
],
},
ja: {
eyebrow: "セルフホストのノート",
title: "Docker と Cloudflare で動く、セルフホストの Evernote 代替",
description:
"EdgeEver はオープンソースで AI ネイティブなノート作業領域です。Evernote の三ペインを残し、Cloudflare 無料枠か、VPS / NAS / 自宅サーバーの Docker で同じアプリを動かせます。",
lead: [
"長く Evernote を使い、信頼できて開かれていて、自分で置ける個人知識ベースが欲しい人向けです。スタックはオープンソース。ノートは自分で制御する SQLite にあり、REST API、MCP、文書化された ENEX 移行があります。",
"タイムライン型の断片ノートでも、ローカル Markdown フォルダでもありません。ノートブックツリー、ノート一覧、編集エリアという古典的な三ペインです。",
],
fitTitle: "向いている人",
fit: [
"タイムラインやファイル倉庫ではなく、Evernote 型のノートブックが欲しい。",
"ノート SaaS や端末数の課金をやめ、自分でホストしたい。",
"Cloudflare 無料枠か、VPS / NAS への Docker 一行インストールが欲しい。",
"Claude Code や Codex などの Agent に、ネイティブ MCP でノートを読ませたい。",
],
notFitTitle: "向いていない人",
notFit: [
"Joplin のような既定のエンドツーエンド暗号が必要なら、EdgeEver は E2EE を売りにしていません。境界は「インスタンスを自分でホストする」ことです。",
"Notion 型のデータベースとブロック作業領域が必要なら、Notion か同類を続けてください。EdgeEver はノート作業領域です。",
"サーバーなしのローカルフォルダだけでよければ、Obsidian や Markdown ディレクトリの方が合うことがあります。",
],
compareTitle: "よくある選択肢との違い",
compareIntro:
"英語圏の検索では Joplin が定番の自前 Evernote 代替です。EdgeEver は古典的な三ペインに近く、Cloudflare 無料枠と Docker の両方を出しています。表はリポジトリと公開ドキュメントで確認できる事実だけです。総合ランキングではありません。",
compareHeaders: ["", "ホスト", "レイアウト", "クライアント", "Evernote 取り込み"],
compareRows: [
["EdgeEver", "Cloudflare 無料枠または Docker", "三ペインのノートブック", "macOS / Windows / Linux / iOS / Android / Clipper", "文書化された ENEX + MCP"],
["Evernote", "ベンダークラウド、自前不可", "三ペインのノートブック", "主要プラットフォーム", "—"],
["Joplin", "Joplin Server または任意の同期", "ノートブック", "公式マルチ端", "ENEX 対応"],
["Obsidian", "ローカルファイル。公式同期は有料", "ファイル倉庫", "主要プラットフォーム", "コミュニティ手段"],
["Memos", "自前運用可", "タイムラインのカード", "Web 中心", "同じ移行経路ではない"],
],
pathsTitle: "次にすること",
paths: [
{
title: "Cloudflare で自前運用",
summary: "サーバーを持ちたくない個人向け。個人規模なら短いノート約 15 万、画像約 5 万が目安。",
href: "/blog/ai-agent-deploy-cloudflare",
cta: "Cloudflare 導入を見る",
},
{
title: "Docker で自前運用",
summary: "同じアプリを、VPS、NAS、自宅サーバーへ一行で入れられます。保存先はディスクや S3 互換バケットで伸ばせます。",
href: "/docker-deploy",
cta: "Docker 導入を見る",
},
{
title: "Evernote から移行",
summary: "ENEX、evernote-backup、EdgeEver MCP で階層、添付、タイムスタンプを残します。",
href: "/blog/evernote-migration-guide",
cta: "Evernote ガイドを見る",
},
{
title: "Notion から移行",
summary: "Notion MCP と EdgeEver MCP を使い、AI アシスタントにページを渡します。",
href: "/blog/notion-migration-guide",
cta: "Notion ガイドを見る",
},
],
faqTitle: "よくある質問",
faqs: [
{
question: "セルフホストの Evernote 代替とは何ですか?",
answer:
"自分で導入し、データは自分のアカウントか機材に置き、Evernote のノートブックと三ペインに近い作業領域を持つノートアプリです。EdgeEver はそのために作り、オープンソース、MCP、Cloudflare / Docker を足しています。",
},
{
question: "Docker で動かせますか?",
answer:
"できます。一行インストーラと、amd64 / arm64、ローカル保存または S3 互換オブジェクトストレージの Docker ガイドがあります。Cloudflare と Docker は同じアプリです。",
},
{
question: "Evernote を取り込めますか?",
answer:
"できます。文書化された流れは ENEX、evernote-backup、MCP 取り込みスクリプトです。作成・更新時刻と入れ子ノートブックを残します。画像付きノートは取り込み後に抜き取り確認してください。",
},
{
question: "Cloudflare の個人利用は本当に無料ですか?",
answer:
"Cloudflare の無料枠内で動かせ、サーバーを別途借りる必要はありません。枠は Cloudflare の方針に従います。足りなくなったら有料プランか Docker へ移せます。",
},
{
question: "iOS と Android アプリはありますか?",
answer:
"あります。Android は Google Play、iOS は App Store です。iOS は現時点で中国本土以外の Apple ID が必要です。Chrome / Edge / Firefox の Clipper もあります。",
},
{
question: "EdgeEver と Evernote の関係は?",
answer:
"提携も後援もありません。EdgeEver は独立したオープンソースの代替です。移行ガイドは、すでに自分で持っているデータだけを扱います。Evernote は権利者の商標です。",
},
],
},
} as const;
+277
View File
@@ -0,0 +1,277 @@
export const jaSiteTagline =
"オープンソース、AI ネイティブ、Cloudflare 無料枠 / Docker で自前運用できる Evernote 代替ノート";
export const jaSiteCopy = {
layout: {
defaultDescription:
"EdgeEver はオープンソースで AI ネイティブな、セルフホスト型のノートと知識ベースです。Evernote の三ペインを残しつつ、macOS、Windows、Linux、iOS、Android、ブラウザ Clipper に対応。Cloudflare の無料枠または Docker で同じアプリを動かせます。",
defaultTitle: `EdgeEver - ${jaSiteTagline}`,
imageAlt: "EdgeEver ノートアプリのスクリーンショット",
ogLocale: "ja_JP",
},
nav: {
homeAria: "EdgeEver ホーム",
features: "機能",
guides: "ガイド",
deploy: "導入",
cloudflareDeploy: "Cloudflare",
dockerDeploy: "Docker",
selfHostedAlternative: "Evernote 代替",
migration: "Evernote から移行",
evernoteMigration: "Evernote から移行",
memosMigration: "Memos から移行",
notionMigration: "Notion から移行",
flomoMigration: "Flomo から移行",
advancedPlay: "AI Agent の使い方",
blog: "ブログ",
backToBlog: "ブログ一覧へ",
contact: "お問い合わせ",
privacy: "プライバシー",
demo: "デモ",
language: "言語",
languageMenu: "言語を切り替える",
tagAll: "すべて",
tagMigration: "移行",
tagMcp: "AI と MCP",
tagSelfHosted: "導入",
openSource: "オープンソース",
},
hero: {
slogan: jaSiteTagline,
popHighlight: "オープンソースの Evernote 代替 · 全プラットフォーム対応",
demo: "ライブデモ",
agentInstall: "AI で導入",
getApps: "アプリ:",
clipper: "Web Clipper",
windows: "Windows",
linux: "Linux",
imageAlt: "EdgeEver のプレビュー",
badgeText:
"💡 対応: macOS、Windows、Linux、iOS、Android、Web Clipper · Cloudflare 無料枠と Docker 一行インストール",
},
bento: {
eyebrow: "WHY EDGEEVER",
heading: "セルフホストの知識管理と AI のために作り直した",
subheading:
"広告、端末数の制限、高いサブスクはやめましょう。速さ、開放性、自分で守れるプライバシーを前提にしています。",
card1: {
badge: "クラシック UI",
title: "三ペインはそのまま、より軽く速く",
desc: "ノートブックツリー、ノート一覧、編集エリアを残しています。リッチテキストと Markdown を切り替えられ、入れ子も全画面フォーカスも使えます。",
subBadge: "リッチテキスト / Markdown 両対応",
treeTitle: "ノートブック",
folder1: "01_Work_Inbox",
folder2: "02_Ideas_Drafts",
folder3: "03_Archive_Notes",
folder4: "↳ 2026_Reading",
listTitle: "ノート (18)",
note1Title: "EdgeEver Architecture",
note1Sub: "Cloudflare Workers / Docker & SQLite...",
note2Title: "Remote MCP Setup",
note2Sub: "Antigravity token setup guide...",
editorTitle: "EdgeEver Architecture",
editorBody: "EdgeEver は Evernote の三ペインに、AI Agent 連携と柔軟な導入を足したものです。",
editorSaveTag: "SQLite に自動保存",
},
card2: {
badge: "AI Native",
title: "ネイティブな AI Agent とエディタ内 LLM",
desc: "Remote MCP で Antigravity、Claude Code、Codex をつなぎ、エディタ内 AI は OpenAI、Claude、Gemini、DeepSeek で要約や編集ができます。",
mockupStatus: "MCP と AI 接続済み",
mockupCmd: "> mcp.search_notes(\"EdgeEver\")",
mockupResult: "関連ノートを見つけ、要約とタグを自動生成しました。",
},
card3: {
badge: "導入の自由度",
title: "Cloudflare 無料枠、または Docker 一行",
desc: "個人利用なら Cloudflare 無料枠に収まることが多く(ノート約 15 万、画像約 5 万)、VPS / NAS / 自宅サーバーなら Docker で同じアプリを動かせます。",
price: "$0",
unit: "/ 月",
sub: "Cloudflare 無料枠の範囲",
metric: "150,000+",
metricSub: "無料枠のノート目安(Docker なら拡張可)",
},
card4: {
badge: "発信向け",
title: "ワンクリックで体裁付きコピー、Mermaid と PDF",
desc: "Markdown をインライン CSS 付き HTML に変換し、Substack や Medium、ニュースレターへ貼れます。Mermaid、KaTeX、PDF プレビューもネイティブです。",
srcTag: "Markdown ソース",
actionBtn: "体裁付きコピー",
previewTitle: "# Heading 1: Rich Text & CSS Styling",
previewBody: "インライン CSS 付きなら、Substack や Medium にすぐ貼れます。",
},
card5: {
badge: "データの主導権",
title: "全プラットフォームと欠損のない ZIP バックアップ",
desc: "macOS、Windows、Linux、ネイティブ iOS、Android、Web Clipper。欠損のない ZIP 書き出しと標準 SQLite で、データは自分のものです。",
archiveTitle: "edgeever-backup.zip",
archiveSub: "Markdown + 添付 + 版履歴",
},
},
marquee: {
title: "全プラットフォーム、柔軟な導入、AI 連携",
subtitle: "macOS、ネイティブ iOS / Android から Docker 自前運用、発信向けコピー、Remote MCP まで。",
items: [
{
tag: "macOS Desktop",
title: "Electron + Rust + SQLite",
desc: "ローカル SQLite ミラーでオフライン記録、差分同期、フォーカスモード",
icon: "bx:bxl-apple",
color: "from-emerald-500/10 to-teal-500/5",
},
{
tag: "iOS Native App",
title: "SwiftUI のネイティブ実装",
desc: "App Store で公開。ネイティブな操作感とオフライン SQLite 同期",
icon: "bx:bx-mobile",
color: "from-teal-500/10 to-emerald-500/5",
},
{
tag: "Android App",
title: "Google Play と署名済み APK",
desc: "Google Play で公開。オフライン記録と共有シートからの取り込み",
icon: "bx:bxl-android",
color: "from-green-500/10 to-emerald-500/5",
},
{
tag: "Web Clipper",
title: "Chrome、Edge、Firefox",
desc: "主要ストアで公開。記事全体とブックマークの取り込み",
icon: "bx:bx-extension",
color: "from-emerald-600/10 to-green-500/5",
},
{
tag: "Docker Deploy",
title: "VPS と NAS の一行インストール",
desc: "curl 一本。ローカルディスクと S3 互換ストレージに対応",
icon: "bx:bxl-docker",
color: "from-teal-600/10 to-emerald-600/5",
},
{
tag: "In-Editor AI",
title: "OpenAI / Claude / Gemini / DeepSeek",
desc: "自分のモデルをつないで要約、校正、執筆",
icon: "bx:bxs-brain",
color: "from-emerald-500/10 to-teal-500/5",
},
{
tag: "Remote MCP",
title: "AI Agent 連携",
desc: "Antigravity、Claude Code、Codex で知識ベースを操作",
icon: "bx:bxs-bot",
color: "from-green-500/10 to-emerald-500/5",
},
{
tag: "Creator Copy",
title: "ワンクリックの体裁付きリッチテキスト",
desc: "Markdown を Substack、Medium、ニュースレター向け HTML に変換",
icon: "bx:bx-copy",
color: "from-emerald-500/10 to-teal-500/5",
},
{
tag: "Rich Media",
title: "Mermaid / KaTeX / PDF",
desc: "構成図と数式をその場で描画。PDF / HTML 書き出し",
icon: "bx:bx-code-block",
color: "from-teal-500/10 to-emerald-500/5",
},
{
tag: "Data Sovereignty",
title: "欠損のない ZIP アーカイブ",
desc: "SQLite の全量書き出し。Markdown、添付、版履歴を含めて復元",
icon: "bx:bx-archive",
color: "from-green-600/10 to-teal-500/5",
},
],
},
features: {
heading: "セルフホスト向けに作り直した個人ノート",
items: [
{
title: "導入の自由度: Cloudflare Serverless と Docker",
summary: "同じアプリを Cloudflare でも、VPS / NAS / 自宅サーバーの Docker でも動かせます。",
points: [
"Cloudflare 無料枠の自前運用: VPS も SSL 証明書も不要。個人ならノート約 15 万、画像約 5 万が目安。",
"Docker 一行導入: curl 一本。ノート数百万件や大きなファイルにも伸ばせます。",
"データの所在: 導入方法を問わず、データは自分のアカウントか自分の機材の中にあります。",
],
},
{
title: "ネイティブな AI Agent とエディタ内の複数モデル",
summary: "REST API、OpenAPI、Remote MCP、複数モデル接続で、AI をノートの相棒にします。",
points: [
"Remote MCP: Antigravity、Claude Code、Codex に読み取り、要約、整理を任せられます。",
"自分のモデル: OpenAI、Anthropic Claude、Google Gemini、DeepSeek、互換エンドポイント。",
"エディタ内の操作: 要約、推敲、翻訳、やることを抽出、続きを書く。",
"周辺連携: Notion データベースや Feishu Bitable とつなぎ、メモを構造化できます。",
],
},
{
title: "クラシックな三ペインとフォーカスモード",
summary: "ノートブックツリー、ノート一覧、編集エリアを残し、リッチテキストと Markdown を両立します。",
points: [
"長期の知識ベース向けに、入れ子のノートブックは制限しません。",
"デスクトップの全画面フォーカス、ノートブックのドラッグ並べ替え、複数ノートの一括移動や結合。",
"TipTap のリッチテキストと Markdown 両ビュー。ノートの版履歴と巻き戻し。",
],
},
{
title: "発信ツールとリッチメディア",
summary: "体裁付きのコピー、Mermaid、KaTeX、PDF プレビュー。",
points: [
"ワンクリックの体裁付きコピー: Markdown をインライン CSS 付き HTML にし、Substack や Medium へ。",
"ネイティブ Mermaid と KaTeX: 構成図、フロー、数式。ソースも編集できます。",
"PDF プレビューと汎用添付: PDF をその場で見られ、Office、アーカイブ、メディアも添付できます。",
"ノート単位の書き出し: Markdown、HTML、PDF。",
],
},
{
title: "全プラットフォームと複数端末の同期",
summary: "macOS、Windows、iOS、Android、ブラウザ Clipper、Web/PWA。商用アプリの端末数制限はありません。",
points: [
"端末数の上限なし: 自前 API なので「2 台まで」のような制限はありません。",
"macOS デスクトップ: Electron + Rust sidecar + SQLite。ローカルは速く、オフライン同期できます。",
"Windows x64: ローカル SQLite と自動更新。現時点は Authenticode 未署名のため、警告が出ることがあります。",
"モバイル: SwiftUI の iOS(App Store)と Android(Google Play と GitHub APK)。",
"Web Clipper: Chrome、Edge、Firefox の公式拡張。",
],
},
{
title: "開かれたデータ、アカウント分離、欠損のない移行",
summary: "標準 SQLite、インスタンス内のアカウント分離、欠損のない ZIP、移行ツール。",
points: [
"複数アカウント: 1 インスタンスで家族や少人数チーム向けに作業領域を分けられます。",
"欠損のない ZIP: Markdown、Front Matter、入れ子ノートブック、添付、版履歴をまとめて書き出し。",
"移行: Evernote (ENEX)、Memos、Notion、Flomo の手順があります。",
"クライアント側の画像圧縮: アップロード前に圧縮(おおよそ 50%〜90%)して転送と保存を抑えます。",
],
},
],
},
guides: {
eyebrow: "EdgeEver Guides",
heading: "導入、移行、AI Agent を動かす",
description:
"最短経路は、自分のインスタンスを立て、既存の Evernote を移し、MCP で AI ワークフローをつなぐことです。",
items: [
{
title: "EdgeEver の導入方法を選ぶ",
summary: "Cloudflare は AI Agent か手動セットアップ。VPS や NAS なら Docker の一行インストーラ。",
href: "/blog/ai-agent-deploy-cloudflare",
cta: "導入ガイドを読む",
},
{
title: "Evernote から移行する",
summary: "EdgeEver MCP、evernote-backup、ENEX 取り込みで、古いライブラリを自前インスタンスへ。",
href: "/blog/evernote-migration-guide",
cta: "移行ガイドを読む",
},
{
title: "AI Agent の応用",
summary: "MCP で、実ノートから知識マップ、タグ整理、一段上の個人知識ワークフローを作れます。",
href: "/guides/advanced-play",
cta: "ワークフローを見る",
},
],
},
} as const;
+67
View File
@@ -0,0 +1,67 @@
import { describe, expect, test } from "bun:test";
import { alternativePageCopy } from "./data/self-hosted-evernote-alternative.ts";
import {
getLocalizedPath,
getSiteLocale,
isExplicitSiteLocalePath,
matchSiteLocale,
siteCopy,
siteLocales,
stripSiteLocalePrefix,
} from "./i18n.ts";
const collectKeys = (value, prefix = "") => {
if (Array.isArray(value)) {
if (value.some((item) => item && typeof item === "object")) {
return value.flatMap((item, index) => collectKeys(item, `${prefix}[${index}]`));
}
return [`${prefix}#${value.length}`];
}
if (!value || typeof value !== "object") {
return [prefix];
}
return Object.keys(value).flatMap((key) => collectKeys(value[key], prefix ? `${prefix}.${key}` : key));
};
describe("site locale routing", () => {
test("reads locale from the path prefix", () => {
expect(getSiteLocale("/")).toBe("zh-CN");
expect(getSiteLocale("/privacy")).toBe("zh-CN");
expect(getSiteLocale("/en")).toBe("en-US");
expect(getSiteLocale("/en/privacy")).toBe("en-US");
expect(getSiteLocale("/ja")).toBe("ja");
expect(getSiteLocale("/ja/privacy")).toBe("ja");
});
test("builds localized paths without stacking prefixes", () => {
expect(getLocalizedPath("zh-CN", "/en/privacy")).toBe("/privacy");
expect(getLocalizedPath("en-US", "/privacy")).toBe("/en/privacy");
expect(getLocalizedPath("ja", "/")).toBe("/ja/");
expect(getLocalizedPath("ja", "/en/contact")).toBe("/ja/contact");
expect(getLocalizedPath("en-US", "/ja/docker-deploy")).toBe("/en/docker-deploy");
expect(getLocalizedPath("ja", "/blog/evernote-migration-guide")).toBe("/ja/blog/evernote-migration-guide");
expect(stripSiteLocalePrefix("/ja/privacy")).toBe("/privacy");
expect(stripSiteLocalePrefix("/ja/")).toBe("/");
});
test("matches Japanese browser languages and treats prefixed paths as explicit", () => {
expect(matchSiteLocale("ja-JP")).toBe("ja");
expect(matchSiteLocale("ja")).toBe("ja");
expect(matchSiteLocale("fr-FR")).toBeNull();
expect(matchSiteLocale("ko-KR")).toBeNull();
expect(isExplicitSiteLocalePath("/ja/privacy")).toBe(true);
expect(isExplicitSiteLocalePath("/en/")).toBe(true);
expect(isExplicitSiteLocalePath("/privacy")).toBe(false);
});
test("keeps Japanese marketing copy aligned with Chinese and English keys", () => {
expect(siteLocales).toEqual(["zh-CN", "en-US", "ja"]);
expect(collectKeys(siteCopy.ja)).toEqual(collectKeys(siteCopy["zh-CN"]));
expect(collectKeys(siteCopy.ja)).toEqual(collectKeys(siteCopy["en-US"]));
expect(collectKeys(alternativePageCopy.ja)).toEqual(collectKeys(alternativePageCopy["zh-CN"]));
expect(siteCopy.ja.nav.backToBlog).toBe("ブログ一覧へ");
});
});
+73 -10
View File
@@ -1,29 +1,85 @@
export type SiteLocale = "zh-CN" | "en-US";
import { jaSiteCopy, jaSiteTagline } from "./i18n-ja";
export const siteLocales = ["zh-CN", "en-US", "ja"] as const;
export type SiteLocale = (typeof siteLocales)[number];
export const defaultSiteLocale: SiteLocale = "zh-CN";
export const unmatchedSiteLocale: SiteLocale = "en-US";
export const siteLocaleStorageKey = "edgeever.site.locale";
export const siteLocaleDataAttribute = "data-edgeever-site-locale";
export const siteLocalePrefixes: Record<SiteLocale, string> = {
"zh-CN": "",
"en-US": "/en",
ja: "/ja",
};
export const siteLocaleLabels: Record<SiteLocale, string> = {
"zh-CN": "简体中文",
"en-US": "English",
ja: "日本語",
};
export const siteTaglines = {
"zh-CN": "开源、原生支持 AI、自由部署(Cloudflare 免费额度 / Docker)的自托管「印象笔记」替代方案",
"en-US": "Open-source, AI-native, self-hosted Evernote alternative with Cloudflare & Docker deployment.",
ja: jaSiteTagline,
} as const satisfies Record<SiteLocale, string>;
export const getSiteLocale = (pathname: string): SiteLocale => (pathname === "/en" || pathname.startsWith("/en/") ? "en-US" : "zh-CN");
const localePrefixPattern = /^\/(en|ja)(?=\/|$)/;
export const stripSiteLocalePrefix = (path: string) => {
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
return normalizedPath.replace(localePrefixPattern, "") || "/";
};
export const matchSiteLocale = (locale: string | null | undefined): SiteLocale | null => {
if (!locale) {
return null;
}
const normalized = locale.trim().replaceAll("_", "-").toLowerCase();
if (normalized === "zh" || normalized.startsWith("zh-")) {
return "zh-CN";
}
if (normalized === "en" || normalized.startsWith("en-")) {
return "en-US";
}
if (normalized === "ja" || normalized.startsWith("ja-")) {
return "ja";
}
return null;
};
export const getSiteLocale = (pathname: string): SiteLocale => {
if (pathname === "/en" || pathname.startsWith("/en/")) {
return "en-US";
}
if (pathname === "/ja" || pathname.startsWith("/ja/")) {
return "ja";
}
return "zh-CN";
};
export const getLocalizedPath = (locale: SiteLocale, path: string) => {
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
const barePath = stripSiteLocalePrefix(path);
const prefix = siteLocalePrefixes[locale];
if (locale === "zh-CN") {
return normalizedPath === "/en" ? "/" : normalizedPath.replace(/^\/en(?=\/|$)/, "") || "/";
if (!prefix) {
return barePath;
}
if (normalizedPath === "/") {
return "/en/";
}
return normalizedPath.startsWith("/en/") ? normalizedPath : `/en${normalizedPath}`;
return barePath === "/" ? `${prefix}/` : `${prefix}${barePath}`;
};
export const isExplicitSiteLocalePath = (pathname: string) => localePrefixPattern.test(pathname);
export const siteCopy = {
"zh-CN": {
layout: {
@@ -48,6 +104,7 @@ export const siteCopy = {
flomoMigration: "从 Flomo 迁移",
advancedPlay: "搭配AI Agent的玩法",
blog: "博客",
backToBlog: "返回博客",
contact: "联系我们",
privacy: "隐私政策",
demo: "在线演示",
@@ -64,6 +121,8 @@ export const siteCopy = {
popHighlight: "印象笔记开源经典平替 · 全平台客户端覆盖",
demo: "在线演示",
agentInstall: "一键 AI 部署",
getApps: "获取客户端:",
clipper: "剪藏插件",
windows: "Windows",
linux: "Linux",
imageAlt: "EdgeEver product preview",
@@ -316,6 +375,7 @@ export const siteCopy = {
flomoMigration: "Migrate from Flomo",
advancedPlay: "AI Agent plays",
blog: "Blog",
backToBlog: "Back to blog",
contact: "Contact",
privacy: "Privacy",
demo: "Demo",
@@ -332,6 +392,8 @@ export const siteCopy = {
popHighlight: "Open-Source Evernote Alternative · Apps Across Platforms",
demo: "Live demo",
agentInstall: "Deploy with AI",
getApps: "Get Apps:",
clipper: "Clipper",
windows: "Windows",
linux: "Linux",
imageAlt: "EdgeEver product preview",
@@ -561,4 +623,5 @@ export const siteCopy = {
],
},
},
ja: jaSiteCopy,
} as const;
+4 -3
View File
@@ -1,14 +1,15 @@
---
import Container from "@/components/container.astro";
import { getFormattedDate } from "@/utils/all";
import { getLocalizedPath, getSiteLocale, siteCopy } from "@/i18n";
import Layout from "./Layout.astro";
const { frontmatter, backHref, backLabel, schemaType = "BlogPosting" } = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site).toString();
const imageUrl = new URL(frontmatter.image.src, Astro.site).toString();
const isEnglish = Astro.url.pathname.startsWith("/en/");
const fallbackBackHref = isEnglish ? "/en/blog" : "/blog";
const fallbackBackLabel = isEnglish ? "Back to blog" : "返回博客";
const locale = getSiteLocale(Astro.url.pathname);
const fallbackBackHref = getLocalizedPath(locale, "/blog");
const fallbackBackLabel = siteCopy[locale].nav.backToBlog;
const articleJsonLd = {
"@context": "https://schema.org",
+41 -33
View File
@@ -2,7 +2,15 @@
import { SEO } from "astro-seo";
import Footer from "@/components/footer.astro";
import Navbar from "@/components/navbar/navbar.astro";
import { getLocalizedPath, getSiteLocale, siteCopy, siteLocaleDataAttribute, siteLocaleStorageKey } from "@/i18n";
import {
getLocalizedPath,
getSiteLocale,
siteCopy,
siteLocaleDataAttribute,
siteLocalePrefixes,
siteLocales,
siteLocaleStorageKey,
} from "@/i18n";
import "@fontsource-variable/inter/index.css";
import "@fontsource-variable/bricolage-grotesque";
import "../styles/global.css";
@@ -26,8 +34,15 @@ export interface Props {
const canonicalURL = new URL(Astro.url.pathname, Astro.site).toString();
const locale = getSiteLocale(Astro.url.pathname);
const copy = siteCopy[locale].layout;
const zhAlternateURL = new URL(getLocalizedPath("zh-CN", Astro.url.pathname), Astro.site).toString();
const enAlternateURL = new URL(getLocalizedPath("en-US", Astro.url.pathname), Astro.site).toString();
const localeAlternateUrls = Object.fromEntries(
siteLocales.map((item) => [
item,
new URL(getLocalizedPath(item, Astro.url.pathname), Astro.site).toString(),
]),
) as Record<(typeof siteLocales)[number], string>;
const zhAlternateURL = localeAlternateUrls["zh-CN"];
const enAlternateURL = localeAlternateUrls["en-US"];
const jaAlternateURL = localeAlternateUrls.ja;
const defaultDescription = copy.defaultDescription;
@@ -57,19 +72,16 @@ const faviconVersion = "edgeever-open-cat-20260809";
const localeRedirectScript = `(() => {
const storageKey = ${JSON.stringify(siteLocaleStorageKey)};
const localeAttribute = ${JSON.stringify(siteLocaleDataAttribute)};
const supportedLocales = ["zh-CN", "en-US"];
const supportedLocales = ${JSON.stringify(siteLocales)};
const prefixes = ${JSON.stringify(siteLocalePrefixes)};
const prefixPattern = /^\\/(en|ja)(?=\\/|$)/;
const normalizeLocale = (locale) => {
if (!locale) return null;
const normalized = String(locale).trim().replaceAll("_", "-").toLowerCase();
if (normalized === "zh" || normalized.startsWith("zh-")) {
return "zh-CN";
}
if (normalized === "en" || normalized.startsWith("en-")) {
return "en-US";
}
if (normalized === "zh" || normalized.startsWith("zh-")) return "zh-CN";
if (normalized === "en" || normalized.startsWith("en-")) return "en-US";
if (normalized === "ja" || normalized.startsWith("ja-")) return "ja";
return null;
};
const getStoredLocale = () => {
@@ -81,7 +93,6 @@ const localeRedirectScript = `(() => {
};
const writeStoredLocale = (locale) => {
if (!supportedLocales.includes(locale)) return;
try {
window.localStorage.setItem(storageKey, locale);
} catch {
@@ -96,33 +107,29 @@ const localeRedirectScript = `(() => {
if (!locale) continue;
sawCandidate = true;
const supported = normalizeLocale(locale);
if (supported) {
return supported;
}
if (supported) return supported;
}
return sawCandidate ? "en-US" : null;
};
const getCurrentLocale = (pathname) => pathname === "/en" || pathname.startsWith("/en/") ? "en-US" : "zh-CN";
const isExplicitLocalePath = (pathname) => pathname === "/en" || pathname.startsWith("/en/");
const getLocalizedPath = (locale, pathname) => {
const getCurrentLocale = (pathname) => {
if (pathname === "/en" || pathname.startsWith("/en/")) return "en-US";
if (pathname === "/ja" || pathname.startsWith("/ja/")) return "ja";
return "zh-CN";
};
const isExplicitLocalePath = (pathname) => prefixPattern.test(pathname);
const stripPrefix = (pathname) => {
const normalizedPath = pathname.startsWith("/") ? pathname : "/" + pathname;
if (locale === "zh-CN") {
return normalizedPath === "/en" ? "/" : normalizedPath.replace(/^\\/en(?=\\/|$)/, "") || "/";
}
if (normalizedPath === "/") {
return "/en/";
}
return normalizedPath.startsWith("/en/") ? normalizedPath : "/en" + normalizedPath;
return normalizedPath.replace(prefixPattern, "") || "/";
};
const getLocalizedPath = (locale, pathname) => {
const barePath = stripPrefix(pathname);
const prefix = prefixes[locale] || "";
if (!prefix) return barePath;
return barePath === "/" ? prefix + "/" : prefix + barePath;
};
const getDesiredLocale = (pathname) => {
if (isExplicitLocalePath(pathname)) {
return null;
}
if (isExplicitLocalePath(pathname)) return null;
const browserLocale = getBrowserLocale();
const storedLocale = getStoredLocale();
@@ -162,6 +169,7 @@ const localeRedirectScript = `(() => {
<link rel="apple-touch-icon" href={`/apple-touch-icon.png?v=${faviconVersion}`} />
<link rel="alternate" hreflang="zh-CN" href={zhAlternateURL} />
<link rel="alternate" hreflang="en-US" href={enAlternateURL} />
<link rel="alternate" hreflang="ja" href={jaAlternateURL} />
<link rel="alternate" hreflang="x-default" href={zhAlternateURL} />
<meta name="theme-color" content="#f8fafc" />
<meta name="generator" content={Astro.generator} />
+139
View File
@@ -0,0 +1,139 @@
---
import { getCollection } from "astro:content";
import Layout from "@/layouts/Layout.astro";
import Container from "@/components/container.astro";
import { getFormattedDate } from "@/utils/all";
import { getLocalizedPath, getSiteLocale, siteCopy } from "@/i18n";
const entries = (await getCollection("blog_en", ({ data }) => !data.draft)).sort(
(a, b) => Number(b.slug === "evernote-migration-guide") - Number(a.slug === "evernote-migration-guide")
|| b.data.publishDate.getTime() - a.data.publishDate.getTime(),
);
const locale = getSiteLocale(Astro.url.pathname);
const copy = siteCopy[locale].nav;
const blogDescription =
"導入、Evernote からの移行、製品の更新、AI Agent の記事です。本文は英語です。";
const blogUrl = new URL(Astro.url.pathname, Astro.site).toString();
const blogJsonLd = {
"@context": "https://schema.org",
"@type": "CollectionPage",
name: "EdgeEver ブログ",
url: blogUrl,
inLanguage: "ja",
description: blogDescription,
};
---
<Layout title="ブログ" description={blogDescription} jsonLd={blogJsonLd}>
<Container>
<section class="py-16 md:py-24">
<p class="text-sm font-semibold text-brand-text">{copy.blog}</p>
<h1 class="mt-3 text-4xl font-bold tracking-tight text-slate-900 md:text-5xl">ブログ</h1>
<p class="mt-5 max-w-2xl text-lg leading-8 text-slate-600">{blogDescription}</p>
<div class="mt-8 flex flex-wrap gap-3">
<a
href={getLocalizedPath(locale, "/blog")}
data-tag-btn="all"
class="rounded-full border border-brand-border/40 bg-brand/5 px-4 py-2 text-sm font-semibold text-brand shadow-sm"
>
{copy.tagAll}
</a>
<a
href={`${getLocalizedPath(locale, "/blog")}?tag=migration`}
data-tag-btn="migration"
class="rounded-full border border-slate-200 bg-slate-50 px-4 py-2 text-sm text-slate-600 hover:bg-slate-100 hover:text-slate-900"
>
{copy.tagMigration}
</a>
<a
href={`${getLocalizedPath(locale, "/blog")}?tag=mcp`}
data-tag-btn="mcp"
class="rounded-full border border-slate-200 bg-slate-50 px-4 py-2 text-sm text-slate-600 hover:bg-slate-100 hover:text-slate-900"
>
{copy.tagMcp}
</a>
<a
href={`${getLocalizedPath(locale, "/blog")}?tag=self-hosted`}
data-tag-btn="self-hosted"
class="rounded-full border border-slate-200 bg-slate-50 px-4 py-2 text-sm text-slate-600 hover:bg-slate-100 hover:text-slate-900"
>
{copy.tagSelfHosted}
</a>
</div>
<div class="mt-12 grid gap-6">
{
entries.map((entry) => (
<a
href={`/en/blog/${entry.slug}`}
data-blog-card
data-tags={entry.data.tags ? entry.data.tags.join(",") : ""}
class="group rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition hover:-translate-y-0.5 hover:border-brand-border hover:shadow-lg"
>
<div class="flex flex-wrap items-center gap-3 text-sm text-slate-500">
<span>{entry.data.category}</span>
<span>·</span>
<time datetime={entry.data.publishDate.toISOString()}>
{getFormattedDate(entry.data.publishDate)}
</time>
<span>·</span>
<span>English</span>
</div>
<h2 class="mt-3 text-2xl font-bold text-slate-900 transition group-hover:text-brand-text">
{entry.data.title}
</h2>
<p class="mt-3 text-base leading-7 text-slate-600">
{entry.data.snippet}
</p>
</a>
))
}
</div>
</section>
</Container>
</Layout>
<script is:inline>
(() => {
const updateFilters = () => {
const urlParams = new URLSearchParams(window.location.search);
const tag = urlParams.get("tag");
const buttons = document.querySelectorAll("[data-tag-btn]");
const activeClasses = ["text-brand", "bg-brand/5", "border-brand-border/40", "font-semibold", "shadow-sm"];
const inactiveClasses = ["text-slate-600", "bg-slate-50", "border-slate-200", "hover:bg-slate-100", "hover:text-slate-900"];
buttons.forEach((btn) => {
const btnTag = btn.getAttribute("data-tag-btn");
const isActive = (!tag && btnTag === "all") || (tag && btnTag === tag);
if (isActive) {
btn.classList.remove(...inactiveClasses);
btn.classList.add(...activeClasses);
} else {
btn.classList.remove(...activeClasses);
btn.classList.add(...inactiveClasses);
}
});
const cards = document.querySelectorAll("[data-blog-card]");
cards.forEach((card) => {
if (!tag) {
card.classList.remove("hidden");
return;
}
const cardTags = (card.getAttribute("data-tags") || "").split(",");
if (cardTags.includes(tag)) {
card.classList.remove("hidden");
} else {
card.classList.add("hidden");
}
});
};
updateFilters();
window.addEventListener("popstate", updateFilters);
})();
</script>
+24
View File
@@ -0,0 +1,24 @@
---
import { getCollection, render } from "astro:content";
import AgentInstallTabs from "@/components/agent-install-tabs.astro";
import BlogLayout from "@/layouts/BlogLayout.astro";
export async function getStaticPaths() {
const entries = await getCollection("blog_en", ({ data }) => !data.draft);
return entries.map((entry) => ({
params: { slug: entry.slug },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await render(entry);
---
<BlogLayout frontmatter={entry.data}>
<p class="not-prose mb-8 rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
技術手順の本文は英語です。ナビゲーションは日本語です。
</p>
{entry.slug === "ai-agent-deploy-cloudflare" && <AgentInstallTabs locale="ja" />}
<Content />
</BlogLayout>
+49
View File
@@ -0,0 +1,49 @@
---
import Container from "@/components/container.astro";
import Layout from "@/layouts/Layout.astro";
import { Icon } from "astro-icon/components";
---
<Layout
title="お問い合わせ"
description="EdgeEver の連絡先とコミュニティ。不具合、機能要望、導入の問題は GitHub Issues が確実です。"
>
<Container>
<main class="mx-auto max-w-3xl py-12 md:py-20 text-slate-700">
<section class="rounded-lg border border-slate-200 bg-white p-8 shadow-sm">
<a
href="https://github.com/tianma-if/edgeever/issues"
target="_blank"
class="group block"
>
<div class="flex items-start gap-4">
<Icon class="text-gray-700 w-8 h-8 shrink-0 mt-0.5 transition group-hover:text-brand-text" name="bx:bxl-github" />
<div>
<h1 class="text-2xl font-bold text-gray-900 transition group-hover:text-brand-text">GitHub Issues</h1>
<p class="mt-3 text-base leading-7 text-slate-500">
ソフトウェアの不具合、導入の失敗、具体的な機能要望はここに書いてください。
</p>
</div>
</div>
</a>
<div class="mt-8 border-t border-slate-200 pt-6">
<h2 class="font-bold text-xl text-gray-900">その他の連絡先</h2>
<div class="mt-5 grid gap-4 sm:grid-cols-2">
<div class="flex items-center gap-3 rounded-lg bg-slate-50 px-4 py-3">
<Icon class="text-gray-400 w-5 h-5 shrink-0" name="bx:bx-message-rounded" />
<span class="text-gray-600">WeChat: m1245207870</span>
</div>
<div class="flex items-center gap-3 rounded-lg bg-slate-50 px-4 py-3">
<Icon class="text-gray-400 w-5 h-5 shrink-0" name="bx:bx-globe" />
<span class="text-gray-600">公式ドメイン: edgeever.org</span>
</div>
</div>
<p class="mt-4 text-xs leading-normal text-slate-400">
個別連絡の際は EdgeEver と書いてください。
</p>
</div>
</section>
</main>
</Container>
</Layout>
@@ -0,0 +1,32 @@
---
import Container from "@/components/container.astro";
import Layout from "@/layouts/Layout.astro";
---
<Layout title="EdgeEver データの削除" description="ノート、添付、端末内データ、自前アカウントを EdgeEver で削除する方法。">
<Container>
<main class="mx-auto max-w-3xl py-12 text-slate-700 md:py-20">
<article class="rounded-lg border border-slate-200 bg-white p-8 shadow-sm md:p-10">
<h1 class="text-3xl font-bold text-slate-950">EdgeEver データの削除</h1>
<div class="mt-8 space-y-8 leading-7">
<section>
<h2 class="text-xl font-bold text-slate-900">ノートと添付の削除</h2>
<p class="mt-3">アプリでノートをゴミ箱へ移し、ゴミ箱を開いて完全削除するか、ゴミ箱を空にしてください。ノートを完全削除すると、接続先の自前インスタンス上の関連添付も削除されます。</p>
</section>
<section>
<h2 class="text-xl font-bold text-slate-900">端末内アプリデータの削除</h2>
<p class="mt-3">アプリでログアウトし、iOS または Android の設定から EdgeEver とそのデータを削除してください。端末上のセッション、SQLite ミラー、下書き、同期キュー、設定が消えます。自前インスタンス上のデータは消えません。</p>
</section>
<section>
<h2 class="text-xl font-bold text-slate-900">アカウントの削除</h2>
<p class="mt-3">EdgeEver のアカウントは、モバイルアプリ開発者ではなく、自前インスタンスの管理者が作成し管理します。アカウントと、あわせて消すべきサーバー側データは、その管理者に依頼してください。自分でインスタンスを運用している場合は、ユーザー管理、または関連する Cloudflare D1 と R2 のデータを削除してください。</p>
</section>
<section>
<h2 class="text-xl font-bold text-slate-900">サポート</h2>
<p class="mt-3">削除手順の質問は <a class="text-teal-700 underline" href="https://github.com/tianma-if/edgeever/issues" target="_blank" rel="noopener noreferrer">EdgeEver の GitHub リポジトリ</a> に Issue を開いてください。パスワード、API トークン、非公開のノート本文は書かないでください。</p>
</section>
</div>
</article>
</main>
</Container>
</Layout>
@@ -0,0 +1,16 @@
---
import DockerDeployGuide from "../../../../../docs/deploy-docker.md";
import GuideLayout from "@/layouts/GuideLayout.astro";
---
<GuideLayout
title="VPS または NAS で Docker を使って EdgeEver を自前運用する"
description="VPS、NAS、自宅サーバーへ、オープンソースのセルフホスト型ノートアプリを一行で入れます。Cloudflare と同じアプリで、SQLite に加えローカルまたは S3 互換ストレージが使えます。"
backHref="/ja/blog/ai-agent-deploy-cloudflare"
backLabel="導入方法の一覧へ"
>
<p class="mb-8 text-slate-600">
技術手順の本文は英語です。ナビゲーションと導入の説明は日本語です。
</p>
<DockerDeployGuide />
</GuideLayout>
@@ -0,0 +1,65 @@
---
import Container from "@/components/container.astro";
import Layout from "@/layouts/Layout.astro";
const title = "EdgeEver AI Agent の応用";
const description = "EdgeEver MCP で、実ノートから人物像、知識マップ、よりきれいなタグ体系を整理させます。";
const prompts = [
{
title: "人物像",
summary: "ノートから長期テーマ、仕事の好み、能力の手がかり、繰り返し現れる問題、最近の方向を抜き出します。",
prompt:
"Use EdgeEver MCP to read my notes and create a persona profile based on the real note content. Judge only from evidence in the notes, do not make psychological diagnoses, and do not exaggerate traits. Include long-term themes, work preferences, capability signals, recurring problems, recent direction, and list related note titles or memo ids after each conclusion.",
},
{
title: "知識マップ",
summary: "ノートを知識領域、重要概念、関連資料、すでに分かっていること、穴に分けます。",
prompt:
"Use EdgeEver MCP to read my notes and organize a knowledge map. Identify the main knowledge areas, key concepts in each area, related notes, what I already understand, and the gaps I still need to fill. Structure the output so it is useful for continued learning and writing.",
},
{
title: "タグ提案",
summary: "既存ノートとタグを見て、重複、狭すぎ、広すぎ、揺れを指摘してから変更します。",
prompt:
"Use EdgeEver MCP to read my notes and existing tags, then design a clearer tag system. Point out duplicate, overly narrow, overly broad, or inconsistently named tags, and suggest merges, renames, and new tags. Do not modify notes yet. Wait for my confirmation before applying changes.",
},
];
const pageUrl = new URL(Astro.url.pathname, Astro.site).toString();
const jsonLd = {
"@context": "https://schema.org",
"@type": "Article",
headline: title,
description,
url: pageUrl,
inLanguage: "ja",
publisher: {
"@type": "Organization",
name: "EdgeEver",
},
};
---
<Layout title={title} description={description} jsonLd={jsonLd}>
<Container>
<main class="mx-auto max-w-4xl py-16 md:py-24">
<p class="text-sm font-semibold text-brand-text">AI Agent Workflow</p>
<h1 class="mt-3 text-4xl font-bold tracking-tight text-slate-900 md:text-5xl">{title}</h1>
<p class="mt-5 text-lg leading-8 text-slate-600">{description}</p>
<p class="mt-3 text-sm text-slate-500">プロンプト本文は英語のままです。Agent にそのまま渡せます。</p>
<div class="mt-10 space-y-5">
{
prompts.map((item) => (
<article class="rounded-lg border border-slate-200 bg-white p-6 shadow-sm">
<h2 class="text-2xl font-bold text-slate-900">{item.title}</h2>
<p class="mt-3 text-base leading-7 text-slate-600">{item.summary}</p>
<pre class="mt-5 overflow-x-auto rounded-lg bg-slate-950 p-4 text-sm leading-6 text-slate-100"><code>{item.prompt}</code></pre>
</article>
))
}
</div>
</main>
</Container>
</Layout>
+49
View File
@@ -0,0 +1,49 @@
---
import Container from "@/components/container.astro";
import Features from "@/components/features.astro";
import Guides from "@/components/guides.astro";
import Hero from "@/components/hero.astro";
import Logos from "@/components/logos.astro";
import MarqueeShowcase from "@/components/marquee-showcase.astro";
import Layout from "@/layouts/Layout.astro";
const siteUrl = new URL(Astro.url.pathname, Astro.site).toString();
const homeJsonLd = [
{
"@context": "https://schema.org",
"@type": "WebSite",
name: "EdgeEver",
alternateName: "EdgeEver Evernote Alternative",
url: siteUrl,
inLanguage: "ja",
description:
"EdgeEver はオープンソース、AI ネイティブ、Cloudflare と Docker で自前運用できる Evernote 代替です。macOS、Windows、Linux、iOS、Android、Web Clipper に対応しています。",
keywords: ["Evernote 代替", "オープンソース ノート", "セルフホスト ノート", "AI ノート"],
},
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "EdgeEver",
alternateName: "EdgeEver Evernote Alternative",
url: siteUrl,
codeRepository: "https://github.com/tianma-if/edgeever",
applicationCategory: "ProductivityApplication",
operatingSystem: "macOS, Windows, Linux, iOS, Android, Web, Chrome, Edge, Firefox",
isAccessibleForFree: true,
description:
"EdgeEver はオープンソース、AI ネイティブなセルフホスト型 Evernote 代替です。三ペインのノート作業領域、全プラットフォームのクライアント、REST API、OpenAPI、MCP を備えています。",
keywords: ["Evernote 代替", "オープンソース ノート", "セルフホスト ノート", "AI ノート"],
},
];
---
<Layout title="" jsonLd={homeJsonLd}>
<Container>
<Hero />
<MarqueeShowcase />
<Features />
<Guides />
<Logos />
</Container>
</Layout>
@@ -0,0 +1,16 @@
---
import ManualDeployGuide from "../../../../../docs/manual-deploy.md";
import GuideLayout from "@/layouts/GuideLayout.astro";
---
<GuideLayout
title="Cloudflare 手動導入ガイド"
description="Cloudflare 上で EdgeEver を手動インストール、設定、更新します。"
backHref="/ja/blog/ai-agent-deploy-cloudflare"
backLabel="導入の概要へ"
>
<p class="mb-8 text-slate-600">
技術手順の本文は英語です。ナビゲーションは日本語です。
</p>
<ManualDeployGuide />
</GuideLayout>
+72
View File
@@ -0,0 +1,72 @@
---
import Container from "@/components/container.astro";
import Layout from "@/layouts/Layout.astro";
---
<Layout
title="プライバシーポリシー"
description="EdgeEver モバイルアプリと Web Clipper のプライバシーポリシー。"
>
<Container>
<main class="mx-auto max-w-3xl py-12 text-slate-700 md:py-20">
<article class="rounded-lg border border-slate-200 bg-white p-8 shadow-sm md:p-10">
<h1 class="text-3xl font-bold text-slate-950">EdgeEver プライバシーポリシー</h1>
<p class="mt-3 text-sm text-slate-500">施行日: 2026年7月18日</p>
<div class="mt-8 space-y-8 leading-7">
<section>
<h2 class="text-xl font-bold text-slate-900">対象</h2>
<p class="mt-3">本ポリシーは EdgeEver の iOS / Android アプリと EdgeEver Web Clipper に適用されます。EdgeEver はオープンソースのセルフホスト型ノートアプリです。接続先のインスタンスはユーザーが選び、運用します。</p>
</section>
<section>
<h2 class="text-xl font-bold text-slate-900">取り扱うデータ</h2>
<ul class="mt-3 list-disc space-y-2 pl-6">
<li>モバイルアプリは、ユーザーがログイン時に入力したインスタンス URL、ユーザー名、パスワードを処理します。得られたセッション資格情報は端末の安全な領域に保存します。</li>
<li>ノートブック、ノート、タグ、添付は端末とユーザーの自前インスタンスの間で送受信されます。高速表示とオフライン編集のため、必要なローカル SQLite ミラー、下書き、同期キュー、設定を端末に置きます。</li>
<li>Web Clipper は、ユーザーが保存を選んだときだけ、ページタイトル、URL、選択範囲、または抽出した本文を処理します。インスタンス URL、API トークン、既定ノートブックはブラウザ拡張のローカルストレージに置きます。</li>
</ul>
</section>
<section>
<h2 class="text-xl font-bold text-slate-900">利用と送信</h2>
<p class="mt-3">モバイルアプリと Web Clipper は、ユーザーが選んだ EdgeEver インスタンスへ直接接続します。プロジェクト運営者は、ノート、資格情報、クリップ内容を受け取る中継や集中サービスを運用していません。</p>
<p class="mt-3">広告、解析、追跡、テレメトリーはありません。広告プラットフォーム、データブローカー、その他第三者へ販売・共有しません。</p>
</section>
<section>
<h2 class="text-xl font-bold text-slate-900">権限とファイルアクセス</h2>
<ul class="mt-3 list-disc space-y-2 pl-6">
<li><strong>ネットワーク</strong>: ユーザーが選んだ EdgeEver インスタンスへのログインと同期。</li>
<li><strong>ファイル</strong>: ユーザーが取り込み、書き出し、添付のアップロードを選んだときだけ、システムのファイル選択を使います。</li>
<li><strong>activeTab</strong>: ユーザーが拡張機能で保存を実行したあとだけ、現在のタブにアクセスします。</li>
<li><strong>scripting</strong>: 現在のページで本文抽出スクリプトを実行します。</li>
<li><strong>storage</strong>: インスタンス URL、API トークン、既定ノートブックを端末内に保存します。</li>
<li><strong>任意のホスト権限</strong>: ユーザーが設定した EdgeEver インスタンスへのネットワークアクセスだけを許可します。</li>
</ul>
</section>
<section>
<h2 class="text-xl font-bold text-slate-900">セキュリティと保持</h2>
<p class="mt-3">HTTPS の EdgeEver インスタンスを使い、アカウントと API トークンを保護してください。端末内のデータは、ログアウト、アプリデータの削除、アンインストールまで残ります。拡張の設定は、変更、拡張データの削除、アンインストールまで残ります。クラウド上のノートと添付はユーザー自身のインスタンスが保持し、そこで編集・削除できます。</p>
</section>
<section>
<h2 class="text-xl font-bold text-slate-900">Chrome Web Store Limited Use</h2>
<p class="mt-3">Chrome API から得た情報は、上記のユーザー向け Web クリップ機能にだけ使います。利用は Chrome Web Store User Data Policy(Limited Use を含む)に従います。</p>
</section>
<section>
<h2 class="text-xl font-bold text-slate-900">Firefox のデータ送信の開示</h2>
<p class="mt-3">Firefox パッケージは Mozilla の分類に従い、認証情報、閲覧アクティビティ、ウェブサイトの内容を申告しています。これは API トークン、現在のページ URL、ユーザーが選んだページ内容で、ユーザーが選んだ EdgeEver インスタンスへ送られます。プロジェクト運営者はこのデータを受け取りません。</p>
</section>
<section>
<h2 class="text-xl font-bold text-slate-900">連絡先</h2>
<p class="mt-3">本ポリシーや EdgeEver のデータ取り扱いについて質問がある場合は、<a class="text-teal-700 underline" href="https://github.com/tianma-if/edgeever/issues" target="_blank" rel="noopener noreferrer">GitHub Issues</a> からメンテナへ連絡してください。</p>
</section>
</div>
</article>
</main>
</Container>
</Layout>
@@ -0,0 +1,5 @@
---
import SelfHostedEvernoteAlternative from "@/components/self-hosted-evernote-alternative.astro";
---
<SelfHostedEvernoteAlternative />