Squash merge feat/expo-app: app-expo, .cursor, workflows, package.json, .husky; remove app-android, app-ios, react-app

This commit is contained in:
Kevin
2026-03-19 01:12:17 +08:00
parent 9e4f301ab9
commit b4f4369b7d
544 changed files with 23707 additions and 67151 deletions

View File

@@ -0,0 +1,10 @@
// This file is automatically generated by i18next-cli, because it was not existing. You can edit it based on your needs: https://www.i18next.com/overview/typescript#custom-type-options
import Resources from './resources.ts';
declare module 'i18next' {
interface CustomTypeOptions {
enableSelector: false;
defaultNS: 'common';
resources: Resources;
}
}

View File

@@ -0,0 +1,158 @@
// This file is automatically generated by i18next-cli. Do not edit manually.
interface Resources {
app: {
languages: {
en: 'English';
system: 'System';
zh: 'Chinese';
};
name: 'Life Echo';
tabs: {
conversations: 'Chats';
explore: 'Explore';
home: 'Home';
memoir: 'Memoir';
profile: 'Profile';
};
theme: {
brand: 'Brand';
default: 'Default';
};
};
auth: {
login: {
codeLabel: 'Verification Code';
getCode: 'Get Code';
getCodeCountdown: 'Retry in {{seconds}}s';
networkError: 'Network error. Please try again later.';
phoneLabel: 'Phone Number';
phonePlaceholder: 'Enter your phone number';
privacyPolicy: 'Privacy Policy';
submit: 'Login';
termsAnd: 'and';
termsIntro: 'I agree to the';
termsRequired: 'Please agree to the User Agreement and Privacy Policy first';
termsRequiredConfirm: 'OK';
termsRequiredTitle: 'Agreement Required';
userAgreement: 'User Agreement';
welcomeSubtitle: 'Some lives grow richer the more you savor them.';
welcomeTitle: 'Welcome back';
};
};
common: {
chapterLabel: '';
chapterReading: {
backgroundColor: '';
bgPureWhite: '';
bgSepia: '';
close: '';
fontSize: '';
readingSettings: '';
typography: '';
};
continueWriting: '';
docs: 'Docs';
emptySubtitle: '';
emptyTitle: '';
readMemory: '';
startChapter: '';
statusDrafting: '';
statusLocked: '';
statusPending: '';
wordsCount: '';
};
conversation: {
addMore: 'More';
agentName: 'Life Echo';
cancelRecording: 'Cancel recording';
chatTitle: 'Conversation';
connectionConnected: 'Connected';
connectionConnecting: 'Connecting...';
connectionDisconnected: 'Disconnected';
emptyGreetingSubtitle: 'Chat with Echo and record your stories.';
greetingTitle: 'Say Hello';
inputPlaceholder: 'Type a message...';
inputPlaceholderVoice: 'Type here or hold the mic to speak...';
me: 'Me';
read: 'Read';
recentChats: 'Recent Chats';
recordingPermissionDenied: 'Microphone permission is required to record';
send: 'Send';
startNewSubtitle: 'Capture a new memory or share your thoughts with Echo.';
switchToText: 'Switch to text input';
switchToVoice: 'Switch to voice input';
tapToEndRecording: 'Tap to end';
tapToStartRecording: 'Tap to start recording';
unread: 'Unread';
viewAll: 'View All';
};
explore: {};
home: {};
memoir: {
chapterLabel: 'Chapter {{index}}';
chapterReading: {
back: 'Back';
backgroundColor: 'Background';
bgPureWhite: 'White';
bgSepia: 'Sepia';
chapterNotFound: 'Chapter not found';
close: 'Close';
fontSans: 'Sans';
fontSerif: 'Serif';
fontSize: 'Font Size';
fontSizeDefault: 'Medium';
fontSizeLarge: 'Large';
fontSizeSmall: 'Small';
readingSettings: 'Reading Settings';
settings: 'Settings';
typography: 'Typography';
};
continueWriting: 'Continue Writing';
emptySubtitle: 'Chat with Echo to record your stories';
emptyTitle: 'No memoir yet';
pageTitle: 'Memoir';
readMemory: 'Read Memory';
startChapter: 'Start Writing';
statusDrafting: 'Drafting';
statusLocked: 'Locked';
statusPending: 'Pending';
wordsCount: '{{count}} words';
};
profile: {
about: {
aboutUs: 'About Us';
title: 'About';
};
appExperience: {
language: 'Language';
languageDesc: 'App display language';
largeText: 'Large Text';
largeTextDesc: 'Make reading easier';
nightMode: 'Night Mode';
nightModeDesc: 'Use dark theme';
theme: 'Theme';
themeDesc: 'App color theme';
title: 'App Experience';
};
dataPrivacy: {
deleteAll: 'Delete All Data';
deleteUnderDevelopment: 'Delete data feature is under development.';
exportAll: 'Export All Data';
exportUnderDevelopment: 'Export feature is under development.';
title: 'Data & Privacy';
};
editAvatar: 'Edit Profile Picture';
helpSupport: {
faq: 'FAQ';
feedback: 'Feedback & Support';
feedbackPageTitle: 'Share your thoughts';
title: 'Help & Support';
};
signOut: 'Sign Out';
signingOut: 'Signing out...';
userNamePlaceholder: 'User';
userTier: '{{tier}}';
};
}
export default Resources;

View File

@@ -0,0 +1,83 @@
import { getLocales } from 'expo-localization';
import { createInstance } from 'i18next';
import { initReactI18next } from 'react-i18next';
import { AppState, type AppStateStatus } from 'react-native';
import {
getAppLanguageOverride,
setLanguageResolver,
} from './language-resolver';
import {
defaultNS,
fallbackLanguage,
namespaces,
resources,
supportedLanguages,
type AppLanguage,
} from './resources';
export { setLanguageResolver };
function resolveLanguage(languageTag?: string | null): AppLanguage {
if (!languageTag) {
return fallbackLanguage;
}
return languageTag.toLowerCase().startsWith('zh') ? 'zh' : 'en';
}
export function getDeviceLanguage(): AppLanguage {
const locale = getLocales()[0];
return resolveLanguage(locale?.languageCode ?? locale?.languageTag);
}
const i18n = createInstance();
if (!i18n.isInitialized) {
void i18n.use(initReactI18next).init({
resources,
ns: [...namespaces],
defaultNS,
fallbackNS: defaultNS,
lng: getDeviceLanguage(),
fallbackLng: fallbackLanguage,
supportedLngs: [...supportedLanguages],
nonExplicitSupportedLngs: true,
initImmediate: false,
returnNull: false,
interpolation: {
escapeValue: false,
},
react: {
useSuspense: false,
},
});
}
/**
* Syncs i18n language: uses app override if set, otherwise device language.
*/
export async function syncLanguageWithDevice() {
const appOverride = await getAppLanguageOverride();
const nextLanguage = appOverride ?? getDeviceLanguage();
if (i18n.resolvedLanguage !== nextLanguage) {
await i18n.changeLanguage(nextLanguage);
}
}
type Removable = {
remove: () => void;
};
export function startLocaleSync(): Removable {
void syncLanguageWithDevice();
return AppState.addEventListener('change', (state: AppStateStatus) => {
if (state === 'active') {
void syncLanguageWithDevice();
}
});
}
export default i18n;

View File

@@ -0,0 +1,22 @@
import type { AppLanguage } from './resources';
type LanguageResolver = () => Promise<AppLanguage | null>;
let resolver: LanguageResolver = () => Promise.resolve(null);
/**
* Injects the app language getter. Called at app startup (e.g. from _layout).
* Enables i18n to resolve user override without importing app-settings directly,
* which keeps i18n testable (no expo-secure-store in test env).
*/
export function setLanguageResolver(getter: LanguageResolver): void {
resolver = getter;
}
/**
* Returns the user's app language override, or null if using device default.
* Used by syncLanguageWithDevice.
*/
export function getAppLanguageOverride(): Promise<AppLanguage | null> {
return resolver();
}

View File

@@ -0,0 +1,18 @@
{
"languages": {
"en": "English",
"system": "System",
"zh": "Chinese"
},
"name": "Life Echo",
"tabs": {
"conversations": "Chats",
"explore": "Explore",
"home": "Home",
"memoir": "Memoir",
"profile": "Profile"
},
"theme": {
"default": "Default"
}
}

View File

@@ -0,0 +1,20 @@
{
"login": {
"codeLabel": "Verification Code",
"getCode": "Get Code",
"getCodeCountdown": "Retry in {{seconds}}s",
"networkError": "Network error. Please try again later.",
"phoneLabel": "Phone Number",
"phonePlaceholder": "Enter your phone number",
"privacyPolicy": "Privacy Policy",
"submit": "Login",
"termsAnd": "and",
"termsIntro": "I agree to the",
"termsRequired": "Please agree to the User Agreement and Privacy Policy first",
"termsRequiredConfirm": "OK",
"termsRequiredTitle": "Agreement Required",
"userAgreement": "User Agreement",
"welcomeSubtitle": "Some lives grow richer the more you savor them.",
"welcomeTitle": "Welcome back"
}
}

View File

@@ -0,0 +1,22 @@
{
"chapterLabel": "",
"chapterReading": {
"backgroundColor": "",
"bgPureWhite": "",
"bgSepia": "",
"close": "",
"fontSize": "",
"readingSettings": "",
"typography": ""
},
"continueWriting": "",
"docs": "Docs",
"emptySubtitle": "",
"emptyTitle": "",
"readMemory": "",
"startChapter": "",
"statusDrafting": "",
"statusLocked": "",
"statusPending": "",
"wordsCount": ""
}

View File

@@ -0,0 +1,26 @@
{
"addMore": "More",
"agentName": "Life Echo",
"cancelRecording": "Cancel recording",
"chatTitle": "Conversation",
"connectionConnected": "Connected",
"connectionConnecting": "Connecting...",
"connectionDisconnected": "Disconnected",
"emptyGreetingSubtitle": "Chat with Echo and record your stories.",
"greetingTitle": "Say Hello",
"inputPlaceholder": "Type a message...",
"inputPlaceholderVoice": "Type here or hold the mic to speak...",
"me": "Me",
"read": "Read",
"recentChats": "Recent Chats",
"recordingPermissionDenied": "Microphone permission is required to record",
"send": "Send",
"startNewSubtitle": "Capture a new memory or share your thoughts with Echo.",
"switchToText": "Switch to text input",
"switchToVoice": "Switch to voice input",
"tapToEndRecording": "Tap to end",
"tapToStartRecording": "Tap to start recording",
"unread": "Unread",
"viewAll": "View All",
"voiceMessagePreview": "Voice message"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,8 @@
{
"chapterReading": {
"back": "Back",
"chapterNotFound": "Chapter not found",
"settings": "Settings"
},
"pageTitle": "Memoir"
}

View File

@@ -0,0 +1,35 @@
{
"about": {
"aboutUs": "About Us",
"title": "About"
},
"appExperience": {
"language": "Language",
"languageDesc": "App display language",
"largeText": "Large Text",
"largeTextDesc": "Make reading easier",
"nightMode": "Night Mode",
"nightModeDesc": "Use dark theme",
"theme": "Theme",
"themeDesc": "App color theme",
"title": "App Experience"
},
"dataPrivacy": {
"deleteAll": "Delete All Data",
"deleteUnderDevelopment": "Delete data feature is under development.",
"exportAll": "Export All Data",
"exportUnderDevelopment": "Export feature is under development.",
"title": "Data & Privacy"
},
"editAvatar": "Edit Profile Picture",
"helpSupport": {
"faq": "FAQ",
"feedback": "Feedback & Support",
"feedbackPageTitle": "Share your thoughts",
"title": "Help & Support"
},
"signingOut": "Signing out...",
"signOut": "Sign Out",
"userNamePlaceholder": "User",
"userTier": "{{tier}}"
}

View File

@@ -0,0 +1,18 @@
{
"languages": {
"en": "英文",
"system": "跟随系统",
"zh": "中文"
},
"name": "岁月时书",
"tabs": {
"conversations": "对话",
"explore": "探索",
"home": "首页",
"memoir": "回忆录",
"profile": "我的"
},
"theme": {
"default": "默认"
}
}

View File

@@ -0,0 +1,20 @@
{
"login": {
"codeLabel": "验证码",
"getCode": "获取验证码",
"getCodeCountdown": "{{seconds}}s 后重试",
"networkError": "网络异常,请稍后重试",
"phoneLabel": "手机号",
"phonePlaceholder": "请输入手机号",
"privacyPolicy": "《隐私政策》",
"submit": "登录",
"termsAnd": "和",
"termsIntro": "我已阅读并同意",
"termsRequired": "请先同意用户协议和隐私政策",
"termsRequiredConfirm": "知道了",
"termsRequiredTitle": "需要同意协议",
"userAgreement": "《用户协议》",
"welcomeSubtitle": "有些人生,越嚼越有味道。",
"welcomeTitle": "欢迎回来"
}
}

View File

@@ -0,0 +1,22 @@
{
"chapterLabel": "chapterLabel",
"chapterReading": {
"backgroundColor": "chapterReading.backgroundColor",
"bgPureWhite": "chapterReading.bgPureWhite",
"bgSepia": "chapterReading.bgSepia",
"close": "chapterReading.close",
"fontSize": "chapterReading.fontSize",
"readingSettings": "chapterReading.readingSettings",
"typography": "chapterReading.typography"
},
"continueWriting": "continueWriting",
"docs": "文档",
"emptySubtitle": "emptySubtitle",
"emptyTitle": "emptyTitle",
"readMemory": "readMemory",
"startChapter": "startChapter",
"statusDrafting": "statusDrafting",
"statusLocked": "statusLocked",
"statusPending": "statusPending",
"wordsCount": "wordsCount"
}

View File

@@ -0,0 +1,26 @@
{
"addMore": "更多功能",
"agentName": "岁月知己",
"cancelRecording": "取消录音发送",
"chatTitle": "对话",
"connectionConnected": "已连接",
"connectionConnecting": "连接中...",
"connectionDisconnected": "未连接",
"emptyGreetingSubtitle": "和 Echo 聊聊,记录你的故事。",
"greetingTitle": "打个招呼",
"inputPlaceholder": "输入消息...",
"inputPlaceholderVoice": "点击这里输入,或者按住左边说话...",
"me": "我",
"read": "已读",
"recentChats": "最近对话",
"recordingPermissionDenied": "需要麦克风权限才能录音",
"send": "发送",
"startNewSubtitle": "记录新回忆,或与 Echo 分享你的想法。",
"switchToText": "切换到文字输入",
"switchToVoice": "切换到语音输入",
"tapToEndRecording": "点击结束",
"tapToStartRecording": "点击开始录音",
"unread": "未读",
"viewAll": "查看全部",
"voiceMessagePreview": "语音消息"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,8 @@
{
"chapterReading": {
"back": "返回",
"chapterNotFound": "章节未找到",
"settings": "设置"
},
"pageTitle": "回忆录"
}

View File

@@ -0,0 +1,35 @@
{
"about": {
"aboutUs": "关于我们",
"title": "关于"
},
"appExperience": {
"language": "语言",
"languageDesc": "应用显示语言",
"largeText": "大字模式",
"largeTextDesc": "让阅读更轻松",
"nightMode": "夜间模式",
"nightModeDesc": "使用深色主题",
"theme": "主题",
"themeDesc": "应用配色主题",
"title": "应用体验"
},
"dataPrivacy": {
"deleteAll": "删除所有数据",
"deleteUnderDevelopment": "删除数据功能开发中,敬请期待。",
"exportAll": "导出所有数据",
"exportUnderDevelopment": "导出功能开发中,敬请期待。",
"title": "数据与隐私"
},
"editAvatar": "编辑头像",
"helpSupport": {
"faq": "常见问题",
"feedback": "反馈与客服",
"feedbackPageTitle": "说说你的想法",
"title": "帮助与支持"
},
"signingOut": "退出中...",
"signOut": "退出登录",
"userNamePlaceholder": "用户",
"userTier": "{{tier}}"
}

View File

@@ -0,0 +1,58 @@
import appEn from '../locales/en/app.json';
import authEn from '../locales/en/auth.json';
import commonEn from '../locales/en/common.json';
import conversationEn from '../locales/en/conversation.json';
import exploreEn from '../locales/en/explore.json';
import homeEn from '../locales/en/home.json';
import memoirEn from '../locales/en/memoir.json';
import profileEn from '../locales/en/profile.json';
import appZh from '../locales/zh/app.json';
import authZh from '../locales/zh/auth.json';
import commonZh from '../locales/zh/common.json';
import conversationZh from '../locales/zh/conversation.json';
import exploreZh from '../locales/zh/explore.json';
import homeZh from '../locales/zh/home.json';
import memoirZh from '../locales/zh/memoir.json';
import profileZh from '../locales/zh/profile.json';
export const supportedLanguages = ['zh', 'en'] as const;
export type AppLanguage = (typeof supportedLanguages)[number];
export const fallbackLanguage: AppLanguage = 'zh';
export const namespaces = [
'app',
'auth',
'common',
'conversation',
'home',
'explore',
'memoir',
'profile',
] as const;
export const defaultNS = 'common' as const;
export const resources = {
zh: {
app: appZh,
auth: authZh,
common: commonZh,
conversation: conversationZh,
home: homeZh,
explore: exploreZh,
memoir: memoirZh,
profile: profileZh,
},
en: {
app: appEn,
auth: authEn,
common: commonEn,
conversation: conversationEn,
home: homeEn,
explore: exploreEn,
memoir: memoirEn,
profile: profileEn,
},
} as const;