feat(portal): i18n-ify DocsView + auth narrative panels

Extract all Chinese from DocsView.vue into docs.* namespace and add
auth.narrative.* sub-namespace for LoginView/RegisterView narrative slots.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mini
2026-04-21 01:42:32 +08:00
parent fc7e27671d
commit 73b3980711
5 changed files with 238 additions and 53 deletions

View File

@@ -11,15 +11,17 @@
</router-link>
<div>
<div class="n-kicker">// 你的订阅,已经付过钱了</div>
<div class="n-kicker">{{ t('auth.narrative.login.kicker') }}</div>
<div class="auth-narrative-headline" style="margin-top: 12px;">
<span class="num-n">N</span> 个订阅<br>
<span class="num-1">1</span> key
<span class="num-n">{{ t('auth.narrative.login.headlineN') }}</span>
{{ ' ' + t('auth.narrative.login.headlineSep') + ' ' }}
<span class="num-1">{{ t('auth.narrative.login.headlineOne') }}</span>
{{ ' ' + t('auth.narrative.login.headlineSuffix') }}
</div>
<p class="auth-narrative-sub">
省去切换账号的繁琐<br>
省去为多个高昂订阅重复买单<br>
<span class="auth-narrative-tagline">PURO纯粹 AI 调用回归本质</span>
{{ t('auth.narrative.login.sub1') }}<br>
{{ t('auth.narrative.login.sub2') }}<br>
<span class="auth-narrative-tagline">{{ t('auth.narrative.login.tagline') }}</span>
</p>
</div>

View File

@@ -11,31 +11,33 @@
</router-link>
<div>
<div class="n-kicker">// 5 分钟开始用</div>
<div class="n-kicker">{{ t('auth.narrative.register.kicker') }}</div>
<div class="auth-narrative-headline" style="margin-top: 12px;">
<span class="num-n">N</span> 个订阅<br>
<span class="num-1">1</span> key
<span class="num-n">{{ t('auth.narrative.register.headlineN') }}</span>
{{ ' ' + t('auth.narrative.register.headlineSep') + ' ' }}
<span class="num-1">{{ t('auth.narrative.register.headlineOne') }}</span>
{{ ' ' + t('auth.narrative.register.headlineSuffix') }}
</div>
<p class="auth-narrative-sub">
省去切换账号的繁琐<br>
省去为多个高昂订阅重复买单<br>
<span class="auth-narrative-tagline">PURO纯粹 AI 调用回归本质</span>
{{ t('auth.narrative.register.sub1') }}<br>
{{ t('auth.narrative.register.sub2') }}<br>
<span class="auth-narrative-tagline">{{ t('auth.narrative.register.tagline') }}</span>
</p>
</div>
<div class="steps">
<div class="steps-title">// 下一步</div>
<div class="steps-title">{{ t('auth.narrative.register.stepsTitle') }}</div>
<div class="step active">
<div class="step-num">1</div>
<div class="step-text"><b>创建账户</b> · 邮箱 + 密码或用 LinuxDO OAuth</div>
<div class="step-text"><b>{{ t('auth.narrative.register.step1Title') }}</b> · {{ t('auth.narrative.register.step1Desc') }}</div>
</div>
<div class="step">
<div class="step-num">2</div>
<div class="step-text"><b>绑定订阅</b> · OAuth 接入你现有的 Claude Pro / ChatGPT Plus</div>
<div class="step-text"><b>{{ t('auth.narrative.register.step2Title') }}</b> · {{ t('auth.narrative.register.step2Desc') }}</div>
</div>
<div class="step">
<div class="step-num">3</div>
<div class="step-text"><b>生成 key</b> · 拿到 <span class="k">sk-puro-</span>换掉 SDK <span class="k">base_url</span></div>
<div class="step-text"><b>{{ t('auth.narrative.register.step3Title') }}</b> · {{ t('auth.narrative.register.step3Desc') }}</div>
</div>
</div>

View File

@@ -11,36 +11,36 @@
<span>PURO AI</span>
</router-link>
<div class="nav-links">
<router-link to="/">产品</router-link>
<router-link to="/pricing">定价</router-link>
<router-link to="/docs" class="active">文档</router-link>
<router-link to="/">{{ $t('docs.nav.products') }}</router-link>
<router-link to="/pricing">{{ $t('docs.nav.pricing') }}</router-link>
<router-link to="/docs" class="active">{{ $t('docs.nav.docs') }}</router-link>
</div>
<div class="nav-cta">
<PuroLocaleSwitcher />
<router-link to="/login" class="btn btn-ghost">登录</router-link>
<router-link to="/register" class="btn btn-primary">注册</router-link>
<router-link to="/login" class="btn btn-ghost">{{ $t('docs.nav.login') }}</router-link>
<router-link to="/register" class="btn btn-primary">{{ $t('docs.nav.signup') }}</router-link>
</div>
</div>
</nav>
<section class="docs-hero container">
<h1>快速接入 PURO AI</h1>
<p class="subtitle">三步走 key base_url 发请求</p>
<h1>{{ $t('docs.hero.title') }}</h1>
<p class="subtitle">{{ $t('docs.hero.subtitle') }}</p>
</section>
<div class="container docs-body">
<section id="get-key" class="docs-section">
<h2>1. 获取 API key</h2>
<p>当前 PURO AI 不开放自助注册付费联系管理员获取</p>
<h2>{{ $t('docs.sections.getKey.heading') }}</h2>
<p>{{ $t('docs.sections.getKey.desc') }}</p>
<div class="callout">
<a href="mailto:admin@puro.im">admin@puro.im</a>
</div>
<p class="note">未来通过 iShare 入口开放订阅购买</p>
<p class="note">{{ $t('docs.sections.getKey.note') }}</p>
</section>
<section id="codex" class="docs-section">
<h2>2. Codex CLI 接入</h2>
<p>修改 <code class="mono">~/.codex/config.toml</code></p>
<h2>{{ $t('docs.sections.codex.heading') }}</h2>
<p>{{ $t('docs.sections.codex.configIntro') }}</p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
@@ -54,7 +54,7 @@
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
{{ $t('docs.sections.codex.copy') }}
</button>
</div>
<pre class="mono"><code>model_provider = <span class="str">"OpenAI"</span>
@@ -67,7 +67,7 @@ base_url = <span class="str">"https://ai.puro.im"</span>
wire_api = <span class="str">"responses"</span>
requires_openai_auth = <span class="kw">true</span></code></pre>
</div>
<p>然后 <code class="mono">~/.codex/auth.json</code></p>
<p>{{ $t('docs.sections.codex.authIntro') }}</p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
@@ -81,14 +81,14 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
{{ $t('docs.sections.codex.copy') }}
</button>
</div>
<pre class="mono"><code>{
<span class="str">"OPENAI_API_KEY"</span>: <span class="str">"sk-xxxxxxxxxxxxxxxx"</span>
}</code></pre>
</div>
<p>验证</p>
<p>{{ $t('docs.sections.codex.verifyIntro') }}</p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
@@ -102,7 +102,7 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
{{ $t('docs.sections.codex.copy') }}
</button>
</div>
<pre class="mono"><code><span class="cm">$</span> codex exec --sandbox read-only <span class="str">"say hi"</span></code></pre>
@@ -110,8 +110,8 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
</section>
<section id="claude-code" class="docs-section">
<h2>3. Claude Code 接入</h2>
<p>修改 <code class="mono">~/.claude/settings.json</code></p>
<h2>{{ $t('docs.sections.claudeCode.heading') }}</h2>
<p>{{ $t('docs.sections.claudeCode.configIntro') }}</p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
@@ -125,7 +125,7 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
{{ $t('docs.sections.claudeCode.copy') }}
</button>
</div>
<pre class="mono"><code>{
@@ -133,12 +133,12 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
<span class="str">"api_key"</span>: <span class="str">"sk-xxxxxxxxxxxxxxxx"</span>
}</code></pre>
</div>
<p class="note">Claude Code 通过 <code class="mono">/v1/messages</code> endpoint 调用 Anthropic 兼容 API</p>
<p class="note">{{ $t('docs.sections.claudeCode.note') }}</p>
</section>
<section id="curl" class="docs-section">
<h2>4. curl 直连测试</h2>
<p>OpenAI Responses API</p>
<h2>{{ $t('docs.sections.curl.heading') }}</h2>
<p>{{ $t('docs.sections.curl.openaiIntro') }}</p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
@@ -152,7 +152,7 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
{{ $t('docs.sections.curl.copy') }}
</button>
</div>
<pre class="mono"><code><span class="cm">$</span> curl https://ai.puro.im/responses \
@@ -160,7 +160,7 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
-H <span class="str">"Content-Type: application/json"</span> \
-d <span class="str">'{"model":"gpt-5.4","input":"hello"}'</span></code></pre>
</div>
<p>Anthropic Messages API</p>
<p>{{ $t('docs.sections.curl.anthropicIntro') }}</p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
@@ -174,7 +174,7 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
{{ $t('docs.sections.curl.copy') }}
</button>
</div>
<pre class="mono"><code><span class="cm">$</span> curl https://ai.puro.im/v1/messages \
@@ -186,15 +186,15 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
</section>
<section id="models" class="docs-section">
<h2>5. 支持的模型</h2>
<h2>{{ $t('docs.sections.models.heading') }}</h2>
<div class="table-wrap">
<table class="models-table mono">
<thead>
<tr>
<th>模型</th>
<th>平台 / 来源</th>
<th>上下文</th>
<th>状态</th>
<th>{{ $t('docs.sections.models.colModel') }}</th>
<th>{{ $t('docs.sections.models.colPlatform') }}</th>
<th>{{ $t('docs.sections.models.colContext') }}</th>
<th>{{ $t('docs.sections.models.colStatus') }}</th>
</tr>
</thead>
<tbody>
@@ -206,7 +206,7 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
</tr>
<tr>
<td><code>gpt-5.4-codex</code></td>
<td><span class="provider gpt"><span class="dot"></span>OpenAI Codex 专用</span></td>
<td><span class="provider gpt"><span class="dot"></span>{{ $t('docs.sections.models.codexDedicated') }}</span></td>
<td>272K</td>
<td><span class="badge-ok">OK</span></td>
</tr>
@@ -237,12 +237,12 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
</tbody>
</table>
</div>
<p class="note">后端 pricing 表实时跟进 <code class="mono">model-price-repo</code>完整清单登录后在 <router-link to="/dashboard">控制台</router-link> 查看</p>
<p class="note">{{ $t('docs.sections.models.note') }}</p>
</section>
<section id="feedback" class="docs-section">
<h2>6. 问题反馈</h2>
<p>遇到问题或希望补接某个平台</p>
<h2>{{ $t('docs.sections.feedback.heading') }}</h2>
<p>{{ $t('docs.sections.feedback.desc') }}</p>
<div class="callout">
<a href="mailto:admin@puro.im">admin@puro.im</a>
</div>
@@ -252,8 +252,11 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import PuroLocaleSwitcher from '@/components/puro/PuroLocaleSwitcher.vue'
const { t } = useI18n()
async function copyCode(ev: MouseEvent) {
const button = ev.currentTarget as HTMLButtonElement
const panel = button.closest('.code-panel')
@@ -262,7 +265,7 @@ async function copyCode(ev: MouseEvent) {
try {
await navigator.clipboard.writeText(codeEl.innerText)
const original = button.innerHTML
button.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor"><path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/></svg> 已复制'
button.innerHTML = `<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor"><path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/></svg> ${t('docs.sections.codex.copied')}`
button.classList.add('copied')
setTimeout(() => {
button.innerHTML = original