···11+import React, { createContext, useContext, useState, useEffect } from 'react'
22+import type { ReactNode } from 'react'
33+import { translations, type Language } from './i18n'
44+55+interface I18nContextType {
66+ language: Language
77+ setLanguage: (lang: Language) => void
88+ t: (key: keyof typeof translations.zh, interpolations?: Record<string, string | number>) => string
99+ isZh: boolean
1010+ isEn: boolean
1111+}
1212+1313+const I18nContext = createContext<I18nContextType | undefined>(undefined)
1414+1515+export const I18nProvider = ({ children }: { children: ReactNode }) => {
1616+ const [language, setLanguageState] = useState<Language>(() => {
1717+ try {
1818+ const saved = localStorage.getItem('language')
1919+ return saved ? JSON.parse(saved) : 'zh'
2020+ } catch {
2121+ return 'zh'
2222+ }
2323+ })
2424+2525+ const setLanguage = (newLanguage: Language) => {
2626+ setLanguageState(newLanguage)
2727+ try {
2828+ localStorage.setItem('language', JSON.stringify(newLanguage))
2929+ } catch (error) {
3030+ console.error('Failed to save language to localStorage:', error)
3131+ }
3232+ }
3333+3434+ const t = (key: keyof typeof translations.zh, interpolations?: Record<string, string | number>) => {
3535+ const currentTranslations = translations[language]
3636+ let text = currentTranslations[key] || translations.zh[key] || key
3737+3838+ if (interpolations) {
3939+ Object.entries(interpolations).forEach(([placeholder, value]) => {
4040+ text = text.replace(new RegExp(`{{${placeholder}}}`, 'g'), String(value))
4141+ })
4242+ }
4343+4444+ return text
4545+ }
4646+4747+ const value: I18nContextType = {
4848+ language,
4949+ setLanguage,
5050+ t,
5151+ isZh: language === 'zh',
5252+ isEn: language === 'en'
5353+ }
5454+5555+ return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
5656+}
5757+5858+export const useI18n = () => {
5959+ const context = useContext(I18nContext)
6060+ if (!context) {
6161+ throw new Error('useI18n must be used within an I18nProvider')
6262+ }
6363+ return context
6464+}
+162
src/i18n.ts
···11+export type Language = 'zh' | 'en'
22+33+export const translations = {
44+ zh: {
55+ title: '动画世代',
66+ subtitle: '点击选择你看过的动画',
77+ website: 'anime-sedai.egoist.dev',
88+ watchedCount: '我看过 {{count}}/{{total}} 部动画',
99+ selectAll: '全选',
1010+ clear: '清除',
1111+ copyImage: '复制图片',
1212+ downloadImage: '下载图片',
1313+ copySuccess: '复制成功',
1414+ downloadSuccess: '下载成功',
1515+ copyFailed: '复制失败: {{error}}',
1616+ downloadFailed: '下载失败: {{error}}',
1717+ copying: '复制中',
1818+ downloading: '下载中',
1919+ unknownError: '未知错误',
2020+ promptType: '锐评提示词',
2121+ promptNormal: '普通',
2222+ promptZako: '杂鱼❤',
2323+ copy: '复制',
2424+ openInChatWise: '在 ChatWise 中打开 (需要先安装)',
2525+ footer: '历年关注最多的动画,数据来自 bgm.tv,由',
2626+ madeBy: '制作,',
2727+ viewCode: '查看代码',
2828+ otherProducts: '作者的其它产品: ',
2929+ aiChatClient: ', 一个优雅的 AI 聊天客户端',
3030+ year: '年',
3131+ watched: '看过',
3232+ notWatched: '没看过',
3333+ none: '无',
3434+ language: '语言',
3535+ chinese: '中文',
3636+ english: 'English'
3737+ },
3838+ en: {
3939+ title: 'Anime Sedai',
4040+ subtitle: 'Click to select anime you have watched',
4141+ website: 'anime-sedai.egoist.dev',
4242+ watchedCount: 'I have watched {{count}}/{{total}} anime',
4343+ selectAll: 'Select All',
4444+ clear: 'Clear',
4545+ copyImage: 'Copy Image',
4646+ downloadImage: 'Download Image',
4747+ copySuccess: 'Copy successful',
4848+ downloadSuccess: 'Download successful',
4949+ copyFailed: 'Copy failed: {{error}}',
5050+ downloadFailed: 'Download failed: {{error}}',
5151+ copying: 'Copying',
5252+ downloading: 'Downloading',
5353+ unknownError: 'Unknown error',
5454+ promptType: 'Review Prompt',
5555+ promptNormal: 'Normal',
5656+ promptZako: 'Zako❤',
5757+ copy: 'Copy',
5858+ openInChatWise: 'Open in ChatWise (installation required)',
5959+ footer: 'Most watched anime by year, data from bgm.tv, made by ',
6060+ madeBy: ', ',
6161+ viewCode: 'View Code',
6262+ otherProducts: 'Other products by the author: ',
6363+ aiChatClient: ', an elegant AI chat client',
6464+ year: '',
6565+ watched: 'Watched',
6666+ notWatched: 'Not Watched',
6767+ none: 'None',
6868+ language: 'Language',
6969+ chinese: '中文',
7070+ english: 'English'
7171+ }
7272+}
7373+7474+export const getPromptTemplate = (lang: Language) => {
7575+ if (lang === 'zh') {
7676+ return {
7777+ normal: `以下是用户的动画观看记录,请生成一个锐评。`,
7878+ zako: `你是一个精通二次元文化的傲娇雌小鬼,需要根据用户提供的动画观看记录,用雌小鬼惯用的嘲讽语气混合动画圈梗生成锐评报告。要求:
7979+1. 结构模板
8080+ - 列出5-6个嘲讽段落
8181+ - 每个段落的所有内容请务必都包含在 ">> 标签" 开始的一行之后!!
8282+ - 每一个嘲讽段落的主题都应当不同,且应当尖锐
8383+ - 你应当大量地使用"杂鱼"、"❤"、"杂鱼~"、"杂鱼❤~","不会吧不会吧"等雌小鬼常用词汇
8484+ - 不要在输出的报告中写题目以及任何 markdown 样式,这非常,非常重要!!
8585+2. 内容规则
8686+ - 一些常见的梗类型:
8787+ - 冷门番暴露癖(例:"看这种没人听的冷门番,大哥哥该不会在等弹幕里出现'同类'吧?杂鱼❤")
8888+ - 补番速度羞辱(例:"三年才看十部?杂鱼哥哥的补番速度比柯南破案还慢呢~")
8989+ - 类型单一化(例:"全是异世界?大哥哥的想象力比史莱姆还黏稠呢~")
9090+ - 标题长度玩梗(例:"《打了300年史莱姆》这种标题...杂鱼哥哥该不会真信能活300年吧?")
9191+ - 所有内容必须包含在 ">> 标签" 行之后
9292+ - 想一下还有哪些适合用来嘲讽的梗,但不要太多,否则会显得很杂乱
9393+3. 示例:
9494+"""
9595+>> 补番龟速の杂鱼❤
9696+9797+不会吧不会吧?从2006到2024年只啃了三部动画?杂鱼哥哥的补番速度比吉良吉影的日常还慢呢❤~等你看完新番,人类都移民火星了杂鱼~杂鱼❤~
9898+9999+>> 冷门番の孤独癖❤
100100+101101+太空丹迪和异世界舅舅?看这种没人讨论的冷门番,杂鱼哥哥该不会在弹幕里蹲"同类"吧❤~不会真以为自己是宇宙第一鉴赏家吧?杂鱼❤~
102102+103103+>> 番量贫瘠の杂鱼❤
104104+105105+三部动画就敢自称阿宅?杂鱼哥哥的番单比史莱姆还稀薄呢❤~不会吧不会吧,该不会把补番当成就系统刷吧?杂鱼❤~杂鱼~
106106+107107+>> ...
108108+109109+...
110110+111111+>> ...
112112+113113+...
114114+"""
115115+116116+现在开始分析用户的动画观看记录,按上述格式输出锐评报告。`
117117+ }
118118+ } else {
119119+ return {
120120+ normal: `The following is the user's anime viewing record, please generate a sharp review.`,
121121+ zako: `You are a proud and arrogant anime otaku girl who needs to generate a sharp review report based on the user's anime viewing record using the sarcastic tone commonly used by tsundere characters mixed with anime culture memes. Requirements:
122122+1. Structure template
123123+ - List 5-6 sarcastic paragraphs
124124+ - All content in each paragraph must be included after a line starting with ">> tag"!!
125125+ - Each sarcastic paragraph should have a different theme and should be sharp
126126+ - You should extensively use terms like "Zako", "❤", "Zako~", "Zako❤~", "No way no way" and other commonly used tsundere vocabulary
127127+ - Do not write titles or any markdown styles in the output report, this is very, very important!!
128128+2. Content rules
129129+ - Some common meme types:
130130+ - Obscure anime exposure fetish (e.g., "Watching such obscure anime that no one talks about, big brother wouldn't be waiting for 'fellow fans' to appear in the comments, would you? Zako❤")
131131+ - Anime watching speed shaming (e.g., "Only ten shows in three years? Big brother's anime completion speed is slower than Conan solving cases~")
132132+ - Type singularity (e.g., "All isekai? Big brother's imagination is stickier than slime~")
133133+ - Title length memes (e.g., "Titles like 'I've Been Killing Slimes for 300 Years'... Zako brother wouldn't really believe you can live 300 years, would you?")
134134+ - All content must be included after ">> tag" lines
135135+ - Think of other suitable memes for sarcasm, but not too many, otherwise it will seem cluttered
136136+3. Example:
137137+"""
138138+>> Turtle-speed anime completion Zako❤
139139+140140+No way no way? Only watched three anime from 2006 to 2024? Big brother's anime completion speed is slower than Kira Yoshikage's daily routine❤~ By the time you finish watching new anime, humans will have migrated to Mars, Zako~Zako❤~
141141+142142+>> Obscure anime loner fetish❤
143143+144144+Space Dandy and Uncle from Another World? Watching such obscure anime that no one discusses, big brother wouldn't be lurking in the comments waiting for "fellow fans", would you❤~ You wouldn't really think you're the universe's number one connoisseur, would you? Zako❤~
145145+146146+>> Meager anime count Zako❤
147147+148148+Three anime and you dare call yourself an otaku? Big brother's anime list is thinner than slime❤~ No way no way, you wouldn't treat anime completion like an achievement system, would you? Zako❤~Zako~
149149+150150+>> ...
151151+152152+...
153153+154154+>> ...
155155+156156+...
157157+"""
158158+159159+Now start analyzing the user's anime viewing record and output a sharp review report according to the above format.`
160160+ }
161161+ }
162162+}