使用示例
以下示例基于当前 api.keykey.im 公开接口,并兼容完整记忆卡片与本地词典兜底响应。
类型安全的查询函数
type MemoryCardResponse = {
success: boolean
data?: {
word: string
displayWord?: string
languageCategory: string
trans?: string[]
practiceTrans?: string[]
definitionRelease?: number
phonetic?: string
phoneticSystem?: string
notation?: string
kanaReading?: string
practiceWord?: string
practiceAliases?: string[]
lexemeKey?: string
examples: Array<{
sentence: string
translation: string
usage?: string
}>
}
error?: string
code?: string
rateLimit?: {
remaining: number
limit: number
reset: string
}
}
async function queryMemoryCard(word: string, lang = 'en') {
const params = new URLSearchParams({
word,
lang,
includeLocalDictionaryFallback: 'true',
})
const response = await fetch(
`https://api.keykey.im/api/public/memory-card?${params}`,
{ cache: 'no-store' },
)
const payload = (await response.json()) as MemoryCardResponse
if (!response.ok || !payload.success || !payload.data) {
throw new Error(payload.error || `HTTP ${response.status}`)
}
return payload
}
正确展示多语言词形
不要把 word、displayWord、practiceWord 和 phonetic 当成同一个字段:
function toWordPresentation(card) {
return {
// 页面标题:优先使用词典规定的大小写或展示形式
title: card.displayWord || card.word,
// 稳定身份:保存到数据库、收藏或复习记录时使用
identity: card.lexemeKey || `${card.languageCategory}:${card.word}`,
// 日语等语言的辅助展示
notation: card.notation,
reading: card.kanaReading,
// 键盘练习答案,不得用 phonetic 代替
practiceAnswer: card.practiceWord,
acceptedAnswers: card.practiceAliases || [],
// 独立的发音值与发音系统
pronunciation: card.phonetic,
pronunciationSystem: card.phoneticSystem,
}
}
例如英语 China 与 china 可能具有不同展示词形和词条身份;日语词条则可能包含:
{
"word": "日本",
"notation": "日本(にっぽん)",
"kanaReading": "にっぽん",
"practiceWord": "nippon",
"practiceAliases": ["nippon"],
"lexemeKey": "ja:日本:にっぽん",
"phonetic": "nippon",
"phoneticSystem": "romaji"
}
完整释义与练习释义
function getDefinitions(card, mode: 'full' | 'practice' = 'full') {
if (mode === 'practice' && card.practiceTrans?.length) {
return {
items: card.practiceTrans,
source: 'system-practice',
release: card.definitionRelease,
}
}
return {
items: card.trans || [],
source: 'full',
release: undefined,
}
}
保存练习释义时,建议同时保存 definitionRelease。这样服务端发布新版本后,客户端可以判断本地释义是否需要更新。
批量查询
async function batchQueryMemoryCards(words, languageCategory = 'en') {
const response = await fetch(
'https://api.keykey.im/api/public/memory-card/batch-query',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
words,
languageCategory,
includeLocalDictionaryFallback: true,
definitionMode: 'full',
}),
},
)
const payload = await response.json()
if (!response.ok || !payload.success) {
throw new Error(payload.error || `HTTP ${response.status}`)
}
return new Map(
payload.data.results
.filter((item) => item.found && item.data)
.map((item) => [item.word, item.data]),
)
}
const cards = await batchQueryMemoryCards(['hello', 'world', 'study'])
批量接口一次最多接收 50 个词条。需要精简练习释义时,可把 definitionMode 改为 practice。
自有缓存
API 返回 Cache-Control: no-store,这是为了让调用方看到最新词典发布。若业务允许缓存,可将语言、规范化查询和释义版本纳入缓存策略:
const CACHE_TTL = 60 * 60 * 1000
function cacheKey(word, lang) {
return `memory-card:${lang}:${word.trim().toLocaleLowerCase(lang)}`
}
async function queryWithLocalCache(word, lang = 'en') {
const key = cacheKey(word, lang)
const raw = localStorage.getItem(key)
if (raw) {
const cached = JSON.parse(raw)
if (Date.now() - cached.savedAt < CACHE_TTL) {
return cached.payload
}
}
const payload = await queryMemoryCard(word, lang)
localStorage.setItem(key, JSON.stringify({ payload, savedAt: Date.now() }))
return payload
}
对于会长期保存到 IndexedDB 或数据库的数据,不应只依赖 TTL;还要比较 definitionRelease,并保留 lexemeKey。
结构化词典内容
英语词条可能包含 dictionaryDetails.openDictionary。使用前应逐层判空:
function OpenDictionarySummary({ card }) {
const details = card.dictionaryDetails?.openDictionary
if (!details) return null
return (
<section>
<h2>{details.headwordSummary}</h2>
<p>{details.memoryHook}</p>
{details.meanings.map((meaning, index) => (
<article key={`${meaning.partOfSpeech}-${index}`}>
<strong>{meaning.partOfSpeech}</strong>
{meaning.shortGloss && <span>{meaning.shortGloss}</span>}
<p>{meaning.learnerExplanation}</p>
</article>
))}
</section>
)
}
dictionaryDetails.ielts 可能提供出现次数、频率组、热力图和真题例句;不同词条的数据完整度可能不同。
配额与错误
const payload = await queryMemoryCard('hello')
const { remaining, limit, reset } = payload.rateLimit
if (remaining < 20) {
console.warn(`配额剩余 ${remaining}/${limit},重置时间:${reset}`)
}
对 RATE_LIMIT_EXCEEDED 和 INSUFFICIENT_QUOTA 不要自动重试;等待 reset。对网络错误和 500 响应,可使用有限次数的指数退避。