@studyjoyful/shared 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # @studyjoyful/shared
2
+
3
+ Shared code for StudyJoyful Web and App.
4
+
5
+ ## 📦 Modules
6
+
7
+ | Module | Description | Exports |
8
+ | ------------- | -------------------- | ----------------------------------------- |
9
+ | **i18n** | Internationalization | `resources`, `namespaces`, `defaultNS` |
10
+ | **constants** | Shared constants | `EXAMPLES_BY_GRADE`, `getExamplesByGrade` |
11
+ | **types** | Type definitions | `ChildGrade`, `Subject`, `Example` |
12
+ | **utils** | Utility functions | `randomSelect`, `shuffle` |
13
+
14
+ ## 🚀 Usage
15
+
16
+ ### Installation (yarn link)
17
+
18
+ ```bash
19
+ # In this package
20
+ cd studyjoyful-shared
21
+ yarn link
22
+
23
+ # In Web
24
+ cd ../studyjoyful-web
25
+ yarn link "@studyjoyful/shared"
26
+
27
+ # In App
28
+ cd ../studyjoyful-app
29
+ yarn link "@studyjoyful/shared"
30
+ ```
31
+
32
+ ### App Metro Config
33
+
34
+ The App requires special Metro configuration:
35
+
36
+ ```javascript
37
+ // metro.config.js
38
+ const sharedPackagePath = path.resolve(__dirname, "../studyjoyful-shared");
39
+
40
+ config.watchFolders = [sharedPackagePath];
41
+ config.resolver.nodeModulesPaths = [
42
+ path.resolve(__dirname, "node_modules"),
43
+ path.resolve(sharedPackagePath, "node_modules"),
44
+ ];
45
+
46
+ const ALIASES = {
47
+ "@studyjoyful/shared": sharedPackagePath + "/src",
48
+ };
49
+ ```
50
+
51
+ ### Import Examples
52
+
53
+ ```typescript
54
+ // i18n
55
+ import { resources, namespaces, defaultNS } from "@studyjoyful/shared";
56
+
57
+ // Constants
58
+ import { EXAMPLES_BY_GRADE, getExamplesByGrade } from "@studyjoyful/shared";
59
+
60
+ // Types
61
+ import type { ChildGrade, Subject, Example } from "@studyjoyful/shared";
62
+
63
+ // Utils
64
+ import { randomSelect, shuffle } from "@studyjoyful/shared";
65
+ ```
66
+
67
+ ## 📁 Structure
68
+
69
+ ```
70
+ src/
71
+ ├── index.ts # Main exports
72
+ ├── i18n/
73
+ │ ├── index.ts
74
+ │ └── locales/
75
+ │ └── en/ # English translations
76
+ │ ├── common.json
77
+ │ ├── auth.json
78
+ │ ├── chat.json
79
+ │ ├── tasks.json
80
+ │ ├── learning.json
81
+ │ ├── profile.json
82
+ │ ├── errors.json
83
+ │ └── sidebar.json
84
+ ├── constants/
85
+ │ ├── index.ts
86
+ │ └── examples.ts # Learning examples
87
+ ├── types/
88
+ │ ├── index.ts
89
+ │ ├── child.ts # ChildGrade, Subject
90
+ │ └── common.ts # PaginationParams
91
+ └── utils/
92
+ ├── index.ts
93
+ └── random.ts # randomSelect, shuffle
94
+ ```
95
+
96
+ ## 🔧 Development
97
+
98
+ ```bash
99
+ # Type check
100
+ npx tsc --noEmit
101
+
102
+ # After changes, test in both:
103
+ # 1. Web (hot reload)
104
+ # 2. App (metro refresh)
105
+ ```
106
+
107
+ ## 📝 Adding New Content
108
+
109
+ 1. Create file in appropriate module
110
+ 2. Export from module's `index.ts`
111
+ 3. Re-export from main `src/index.ts`
112
+ 4. Test in both Web and App
113
+
114
+ ---
115
+
116
+ **Package**: `@studyjoyful/shared`
117
+ **Version**: 1.0.0
118
+ **Consumers**: studyjoyful-web, studyjoyful-app
119
+ **Language**: English only (Singapore market)
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@studyjoyful/shared",
3
+ "version": "1.0.0",
4
+ "description": "Shared code for StudyJoyful Web and App (i18n, constants, types, utils)",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "files": [
8
+ "src"
9
+ ],
10
+ "scripts": {
11
+ "lint": "eslint src",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "keywords": [
15
+ "studyjoyful",
16
+ "shared",
17
+ "i18n",
18
+ "constants",
19
+ "types"
20
+ ],
21
+ "author": "StudyJoyful",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "i18next": "^25.6.3",
25
+ "react-i18next": "^16.3.5"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.14.0",
32
+ "typescript": "^5.7.3"
33
+ }
34
+ }
35
+
@@ -0,0 +1,215 @@
1
+ /**
2
+ * ============================================
3
+ * Learning Examples Configuration
4
+ * ============================================
5
+ *
6
+ * Examples organized by grade (P1/P2/P3) and subject (Math/English).
7
+ * Used in chat welcome screen to provide grade-appropriate examples.
8
+ *
9
+ * Product Positioning:
10
+ * - Target: Parents of P1-P3 children in Singapore (6-9 years)
11
+ * - Subjects: Math + English
12
+ * - Core Value: Turn homework into 10-minute mini-game practice
13
+ *
14
+ * Display Rules:
15
+ * - App: 1 Math + 1 English (randomly selected)
16
+ * - Web: 2 Math + 2 English
17
+ *
18
+ * Guidelines:
19
+ * - Prompts should sound like natural parent speech (casual, concise)
20
+ * - Topics should match Singapore MOE curriculum
21
+ * - Avoid ambiguous terms (e.g., "Sight Words" needs specific words)
22
+ */
23
+
24
+ import type { ChildGrade, Subject } from '../types';
25
+ import { randomSelect } from '../utils';
26
+
27
+ /**
28
+ * Example data interface
29
+ */
30
+ export interface Example {
31
+ /** Emoji icon */
32
+ emoji: string;
33
+ /** Subject */
34
+ subject: Subject;
35
+ /** Grade label (for display) */
36
+ gradeHint: string;
37
+ /** Example title */
38
+ title: string;
39
+ /** Prompt to send to AI */
40
+ prompt: string;
41
+ /** Highlight keyword (optional) */
42
+ highlight?: string;
43
+ }
44
+
45
+ /**
46
+ * Examples organized by grade and subject
47
+ *
48
+ * Each grade has 2 examples per subject for variety.
49
+ * All prompts are in English (Singapore market).
50
+ */
51
+ export const EXAMPLES_BY_GRADE: Record<ChildGrade, { Math: Example[]; English: Example[] }> = {
52
+ P1: {
53
+ Math: [
54
+ {
55
+ emoji: '🔢',
56
+ subject: 'Math',
57
+ gradeHint: 'P1',
58
+ title: 'Addition within 10',
59
+ prompt: 'Addition within 10, like 3+5',
60
+ highlight: '3+5',
61
+ },
62
+ {
63
+ emoji: '➖',
64
+ subject: 'Math',
65
+ gradeHint: 'P1',
66
+ title: 'Subtraction within 10',
67
+ prompt: 'Subtraction within 10, like 9-4',
68
+ highlight: '9-4',
69
+ },
70
+ ],
71
+ English: [
72
+ {
73
+ emoji: '🔤',
74
+ subject: 'English',
75
+ gradeHint: 'P1',
76
+ title: 'Sight Words',
77
+ prompt: 'Sight words: the, and, is, it',
78
+ highlight: 'the, and, is',
79
+ },
80
+ {
81
+ emoji: '📖',
82
+ subject: 'English',
83
+ gradeHint: 'P1',
84
+ title: 'Simple Sentences',
85
+ prompt: 'Simple sentences like "I like apples"',
86
+ highlight: 'I like apples',
87
+ },
88
+ ],
89
+ },
90
+ P2: {
91
+ Math: [
92
+ {
93
+ emoji: '🔢',
94
+ subject: 'Math',
95
+ gradeHint: 'P2',
96
+ title: 'Addition with Carrying',
97
+ prompt: 'Addition with carrying, like 38+25',
98
+ highlight: '38+25',
99
+ },
100
+ {
101
+ emoji: '➖',
102
+ subject: 'Math',
103
+ gradeHint: 'P2',
104
+ title: 'Subtraction with Borrowing',
105
+ prompt: 'Subtraction with borrowing, like 52-28',
106
+ highlight: '52-28',
107
+ },
108
+ ],
109
+ English: [
110
+ {
111
+ emoji: '📖',
112
+ subject: 'English',
113
+ gradeHint: 'P2',
114
+ title: 'Spelling Words',
115
+ prompt: 'Spelling: beautiful, because, different',
116
+ highlight: 'spelling',
117
+ },
118
+ {
119
+ emoji: '✏️',
120
+ subject: 'English',
121
+ gradeHint: 'P2',
122
+ title: 'Emotion Words',
123
+ prompt: 'Emotion words: happy, sad, angry',
124
+ highlight: 'happy, sad',
125
+ },
126
+ ],
127
+ },
128
+ P3: {
129
+ Math: [
130
+ {
131
+ emoji: '✖️',
132
+ subject: 'Math',
133
+ gradeHint: 'P3',
134
+ title: 'Times Tables',
135
+ prompt: '7 and 8 times tables',
136
+ highlight: 'times tables',
137
+ },
138
+ {
139
+ emoji: '➗',
140
+ subject: 'Math',
141
+ gradeHint: 'P3',
142
+ title: 'Division Practice',
143
+ prompt: 'Division like 56÷8',
144
+ highlight: '56÷8',
145
+ },
146
+ ],
147
+ English: [
148
+ {
149
+ emoji: '📝',
150
+ subject: 'English',
151
+ gradeHint: 'P3',
152
+ title: 'Past Tense',
153
+ prompt: 'Past tense verbs',
154
+ highlight: 'past tense',
155
+ },
156
+ {
157
+ emoji: '📚',
158
+ subject: 'English',
159
+ gradeHint: 'P3',
160
+ title: 'Reading Comprehension',
161
+ prompt: 'Reading comprehension practice',
162
+ highlight: 'reading',
163
+ },
164
+ ],
165
+ },
166
+ };
167
+
168
+ /**
169
+ * Get all examples as a flat array (for random selection)
170
+ */
171
+ export function getAllExamples(): Example[] {
172
+ const all: Example[] = [];
173
+ for (const grade of Object.keys(EXAMPLES_BY_GRADE) as ChildGrade[]) {
174
+ all.push(...EXAMPLES_BY_GRADE[grade].Math);
175
+ all.push(...EXAMPLES_BY_GRADE[grade].English);
176
+ }
177
+ return all;
178
+ }
179
+
180
+ /**
181
+ * Get examples by grade
182
+ *
183
+ * @param grade - Child's grade (P1/P2/P3), if null select randomly from all
184
+ * @param mathCount - Number of Math examples (Web: 2, App: 1)
185
+ * @param englishCount - Number of English examples (Web: 2, App: 1)
186
+ * @returns Selected examples array
187
+ */
188
+ export function getExamplesByGrade(
189
+ grade: ChildGrade | null,
190
+ mathCount: number,
191
+ englishCount: number
192
+ ): Example[] {
193
+ if (grade) {
194
+ // Has grade info, select from that grade
195
+ const gradeExamples = EXAMPLES_BY_GRADE[grade];
196
+ const mathExamples = randomSelect(gradeExamples.Math, mathCount);
197
+ const englishExamples = randomSelect(gradeExamples.English, englishCount);
198
+ return [...mathExamples, ...englishExamples];
199
+ }
200
+
201
+ // No grade info, randomly select from all examples
202
+ const allMath: Example[] = [];
203
+ const allEnglish: Example[] = [];
204
+
205
+ for (const g of Object.keys(EXAMPLES_BY_GRADE) as ChildGrade[]) {
206
+ allMath.push(...EXAMPLES_BY_GRADE[g].Math);
207
+ allEnglish.push(...EXAMPLES_BY_GRADE[g].English);
208
+ }
209
+
210
+ const mathExamples = randomSelect(allMath, mathCount);
211
+ const englishExamples = randomSelect(allEnglish, englishCount);
212
+
213
+ return [...mathExamples, ...englishExamples];
214
+ }
215
+
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared Constants
3
+ *
4
+ * Common constants and configurations used across Web and App.
5
+ */
6
+
7
+ export * from './examples';
8
+
@@ -0,0 +1,37 @@
1
+ /**
2
+ * i18n Module
3
+ *
4
+ * Centralized translations for StudyJoyful Web and App.
5
+ * English only (Singapore market).
6
+ */
7
+
8
+ import en from './locales/en';
9
+
10
+ // All available namespaces
11
+ export const namespaces = [
12
+ 'common',
13
+ 'auth',
14
+ 'chat',
15
+ 'tasks',
16
+ 'learning',
17
+ 'profile',
18
+ 'errors',
19
+ 'sidebar',
20
+ ] as const;
21
+
22
+ export type Namespace = (typeof namespaces)[number];
23
+
24
+ // Resources object for i18next
25
+ export const resources = {
26
+ en,
27
+ } as const;
28
+
29
+ // Default namespace
30
+ export const defaultNS = 'common';
31
+
32
+ // Default language
33
+ export const defaultLanguage = 'en';
34
+
35
+ // Re-export locale content
36
+ export { en };
37
+
@@ -0,0 +1,80 @@
1
+ {
2
+ "signIn": {
3
+ "title": "StudyJoyful",
4
+ "subtitle": "Joyful Learning Starts Here",
5
+ "welcomeBack": "Welcome Back",
6
+ "continueWith": "Sign in to continue using StudyJoyful",
7
+ "email": "Email Address",
8
+ "emailPlaceholder": "your@email.com",
9
+ "sendCode": "Send Verification Code",
10
+ "or": "or",
11
+ "withGoogle": "Sign in with Google",
12
+ "verificationCode": "Verification Code",
13
+ "codeSentTo": "Sent to {{email}}",
14
+ "codePlaceholder": "Enter 6-digit code",
15
+ "verifyAndSignIn": "Verify & Sign In",
16
+ "back": "← Back",
17
+ "noAccount": "Don't have an account?",
18
+ "signUpNow": "Sign Up"
19
+ },
20
+ "signUp": {
21
+ "title": "Create Account",
22
+ "subtitle": "Start Your Smart Learning Journey",
23
+ "email": "Email Address",
24
+ "emailPlaceholder": "your@email.com",
25
+ "sendCode": "Send Verification Code",
26
+ "or": "or",
27
+ "withGoogle": "Sign up with Google",
28
+ "verificationCode": "Verification Code",
29
+ "codeSentTo": "Sent to {{email}}",
30
+ "codePlaceholder": "Enter 6-digit code",
31
+ "verifyAndSignUp": "Verify & Sign Up",
32
+ "back": "← Back",
33
+ "hasAccount": "Already have an account?",
34
+ "signInNow": "Sign In",
35
+ "termsAgreement": "By creating an account, you agree to our",
36
+ "termsOfService": "Terms of Service",
37
+ "and": "and",
38
+ "privacyPolicy": "Privacy Policy"
39
+ },
40
+ "errors": {
41
+ "signInFailed": "Sign In Failed",
42
+ "signUpFailed": "Sign Up Failed",
43
+ "checkEmail": "Please check if the email address is correct",
44
+ "emailAlreadyRegistered": "This email may already be registered, please try signing in",
45
+ "verificationFailed": "Verification Failed",
46
+ "tryAgain": "Please try again",
47
+ "incorrectCode": "Incorrect Code",
48
+ "checkCode": "Please check if the code is correct",
49
+ "codeExpired": "Code Expired",
50
+ "resendCode": "Please go back and resend the code",
51
+ "retryOrResend": "Please retry or resend the code",
52
+ "googleSignInFailed": "Google sign in failed, please retry",
53
+ "googleSignUpFailed": "Google sign up failed, please retry",
54
+ "additionalInfoRequired": "Additional Information Required",
55
+ "checkClerkConfig": "Please check your Clerk Dashboard settings, or use Google sign in"
56
+ },
57
+ "accountNotFound": {
58
+ "title": "Account Not Found",
59
+ "description": "This email is not registered yet. Would you like to sign up?",
60
+ "cancel": "Cancel",
61
+ "goSignUp": "Sign Up"
62
+ },
63
+ "devLogin": {
64
+ "button": "🔧 Quick Login (Dev)",
65
+ "logging": "Logging in...",
66
+ "badge": "Dev Mode",
67
+ "hint": "Skip OAuth, for development only"
68
+ },
69
+ "loading": {
70
+ "checkingAuth": "Checking login status...",
71
+ "loadingUser": "Loading user info...",
72
+ "retrying": "Retrying..."
73
+ },
74
+ "guard": {
75
+ "networkError": "Network Connection Failed",
76
+ "networkErrorDesc": "Please check your network and try again",
77
+ "loadError": "Failed to Load",
78
+ "loadErrorDesc": "Unable to load user info, please try again later"
79
+ }
80
+ }
@@ -0,0 +1,134 @@
1
+ {
2
+ "welcome": {
3
+ "title": "What to practice today?",
4
+ "subtitle": "Just tell me the topic — practice ready in seconds",
5
+ "tipsTitle": "💡 Good to know",
6
+ "tips": {
7
+ "tip1": "Upload a homework photo for instant analysis",
8
+ "tip2": "Customize: 3-10 questions, easier or harder",
9
+ "tip3": "Each session takes about 10 minutes"
10
+ },
11
+ "uploadHint": "Supports photo upload",
12
+ "examplesTitle": "Try these examples",
13
+ "gradeExamples": "Examples for {{grade}}"
14
+ },
15
+ "input": {
16
+ "placeholder": {
17
+ "default": "Type homework topic or upload photo...",
18
+ "complete": "Task complete ✅",
19
+ "streaming": "AI generating...",
20
+ "continue": "Continue conversation..."
21
+ },
22
+ "imageCompressing": "Compressing image...",
23
+ "imageSelected": "Image selected",
24
+ "uploadImage": "Upload image (auto-compress)",
25
+ "send": "Send (Enter)",
26
+ "processing": "Processing...",
27
+ "voiceComingSoon": "Voice input (coming soon)",
28
+ "sendHint": "Enter to send, Shift + Enter for new line",
29
+ "dragDropHint": " · Supports drag & drop or paste images",
30
+ "analyzeImage": "Please analyze this image"
31
+ },
32
+ "recovery": {
33
+ "title": "Unfinished Conversation Found",
34
+ "description": "You have an incomplete task creation in progress. Would you like to continue?",
35
+ "continue": "Continue",
36
+ "discard": "Start Over",
37
+ "discarding": "Clearing..."
38
+ },
39
+ "status": {
40
+ "idle": "New",
41
+ "clarify_profile": "Gathering Info",
42
+ "clarify_content": "Understanding Request",
43
+ "ready": "Ready to Create",
44
+ "generating": "Generating",
45
+ "generatingContent": "Generating learning content...",
46
+ "complete": "Complete",
47
+ "reject": "Cancelled",
48
+ "error": "Error"
49
+ },
50
+ "progress": {
51
+ "lesson": "Lesson",
52
+ "lessonActive": "Generating lesson",
53
+ "lessonsReady": "Lessons ready",
54
+ "question": "Question",
55
+ "questionActive": "Generating questions",
56
+ "questionsReady": "Questions ready",
57
+ "validating": "Validating",
58
+ "validatingActive": "Validating questions",
59
+ "validatingProgress": "Validating {{current}}/{{total}}",
60
+ "validatingItem": "Validating #{{current}}",
61
+ "correcting": "Optimizing",
62
+ "correctingActive": "Optimizing questions",
63
+ "correctingProgress": "Optimizing {{current}}/{{total}}",
64
+ "correctingProgressWithAttempt": "Optimizing {{current}}/{{total}} (attempt {{attempt}})",
65
+ "correctingItem": "Optimizing #{{current}}",
66
+ "correctingItemWithAttempt": "Optimizing #{{current}} (attempt {{attempt}})"
67
+ },
68
+ "taskDraft": {
69
+ "completed": "Task Generated",
70
+ "draft": "Task Draft",
71
+ "subject": "Subject",
72
+ "topic": "Topic",
73
+ "goal": "Goal",
74
+ "grade": "Grade",
75
+ "previewContent": "Preview Content"
76
+ },
77
+ "taskCreated": {
78
+ "title": "Task Created! 🎉",
79
+ "subtitle": "Your learning task is ready",
80
+ "startLearning": "Start Learning",
81
+ "preview": "Preview Content",
82
+ "share": "Share with Child",
83
+ "createAnother": "Create Another Task",
84
+ "lesson": "Lesson",
85
+ "sections": "{{count}} sections",
86
+ "exercise": "Exercise",
87
+ "questions": "{{count}} questions",
88
+ "shareSuccess": "Link copied to clipboard",
89
+ "shareFailed": "Copy failed, please copy manually",
90
+ "noShareLink": "No share link available"
91
+ },
92
+ "history": {
93
+ "title": "Recent Conversations",
94
+ "empty": "No conversations yet",
95
+ "loadFailed": "Failed to load",
96
+ "retryLoad": "Tap to retry",
97
+ "new": "New",
98
+ "deleteTitle": "Delete Conversation",
99
+ "deleteConfirm": "Delete \"{{title}}\"? This cannot be undone.",
100
+ "deleting": "Deleting...",
101
+ "messages": "{{count}} messages",
102
+ "viewTask": "View Task",
103
+ "taskGenerated": "Task Generated"
104
+ },
105
+ "abandon": {
106
+ "title": "Create new chat?",
107
+ "description": "Current conversation is incomplete. After creating a new chat, you can find this conversation in the history on the left.",
108
+ "stay": "Continue current",
109
+ "leave": "New chat"
110
+ },
111
+ "loading": {
112
+ "checking": "Loading...",
113
+ "history": "Loading conversation...",
114
+ "failed": "Failed to Load",
115
+ "expired": "Conversation Expired",
116
+ "backToNew": "Back to New Conversation"
117
+ },
118
+ "header": {
119
+ "newTask": "StudyJoyful",
120
+ "creating": "Creating Task"
121
+ },
122
+ "time": {
123
+ "justNow": "Just now",
124
+ "minutesAgo": "{{count}} min ago",
125
+ "hoursAgo": "{{count}} hours ago",
126
+ "daysAgo": "{{count}} days ago"
127
+ },
128
+ "math": {
129
+ "renderError": "Formula render error:"
130
+ },
131
+ "errors": {
132
+ "imageProcessingFailed": "Image Processing Failed"
133
+ }
134
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "loading": "Loading...",
3
+ "loadingContent": "Loading learning content...",
4
+ "error": "Error",
5
+ "success": "Success",
6
+ "retry": "Retry",
7
+ "cancel": "Cancel",
8
+ "save": "Save",
9
+ "delete": "Delete",
10
+ "edit": "Edit",
11
+ "close": "Close",
12
+ "confirm": "Confirm",
13
+ "back": "Back",
14
+ "next": "Next",
15
+ "done": "Done",
16
+ "submit": "Submit",
17
+ "continue": "Continue",
18
+ "skip": "Skip",
19
+ "search": "Search",
20
+ "refresh": "Refresh",
21
+ "viewAll": "View All",
22
+ "noData": "No data",
23
+ "copiedToClipboard": "Copied to clipboard",
24
+ "today": "Today",
25
+ "yesterday": "Yesterday",
26
+ "daysAgo": "{{count}} days ago",
27
+ "share": "Share",
28
+ "settings": "Settings",
29
+ "createTask": "Create Learning Task",
30
+ "imageSelect": {
31
+ "title": "Select Image",
32
+ "takePhoto": "📷 Take Photo",
33
+ "chooseFromGallery": "🖼️ Choose from Gallery"
34
+ },
35
+ "imagePreview": {
36
+ "selected": "1 image selected"
37
+ }
38
+ }
39
+
@@ -0,0 +1,71 @@
1
+ {
2
+ "network": {
3
+ "offline": "You're offline",
4
+ "offlineDesc": "Check your internet connection and try again",
5
+ "noConnection": "No network connection, please check and retry",
6
+ "timeout": "Request timed out",
7
+ "timeoutDesc": "The server is taking too long. Please try again."
8
+ },
9
+ "server": {
10
+ "unavailable": "Server Unavailable",
11
+ "unavailableDesc": "We're experiencing issues. Please try again later.",
12
+ "maintenance": "Under Maintenance",
13
+ "maintenanceDesc": "We'll be back shortly. Thank you for your patience."
14
+ },
15
+ "auth": {
16
+ "sessionExpired": "Session Expired",
17
+ "sessionExpiredDesc": "Please sign in again to continue.",
18
+ "unauthorized": "Access Denied"
19
+ },
20
+ "validation": {
21
+ "required": "This field is required",
22
+ "invalidEmail": "Invalid email address",
23
+ "tooShort": "Must be at least {{min}} characters",
24
+ "tooLong": "Must be at most {{max}} characters"
25
+ },
26
+ "generic": {
27
+ "title": "Something went wrong",
28
+ "description": "An unexpected error occurred. Please try again.",
29
+ "retry": "Retry",
30
+ "tryAgain": "Try Again",
31
+ "goBack": "Go Back",
32
+ "goHome": "Go to Home"
33
+ },
34
+ "request": {
35
+ "failed": "Request failed",
36
+ "failedWithStatus": "Request failed ({{status}})",
37
+ "connectionFailed": "Connection failed, please retry",
38
+ "noResponseBody": "No response body"
39
+ },
40
+ "image": {
41
+ "processingFailed": "Image processing failed",
42
+ "selectFailed": "Failed to select image, please retry",
43
+ "cameraFailed": "Failed to take photo, please retry",
44
+ "permissionGallery": "Gallery permission required to select images",
45
+ "permissionCamera": "Camera permission required to take photos",
46
+ "tooLarge": "Image still too large after compression",
47
+ "invalidType": "Please select an image file",
48
+ "maxCountExceeded": "Maximum {{count}} images allowed",
49
+ "placeholder": "[Image]"
50
+ },
51
+ "codes": {
52
+ "AUTH_001": "Please sign in first",
53
+ "AUTH_002": "Session expired, please sign in again",
54
+ "AUTH_003": "Invalid credentials",
55
+ "NETWORK_OFFLINE": "No internet connection",
56
+ "AI_001": "AI service temporarily unavailable",
57
+ "AI_002": "Too many requests, please try again later",
58
+ "SYS_001": "Server error, please try again later",
59
+ "SYS_002": "Resource not found",
60
+ "SYS_003": "Validation error",
61
+ "SYS_004": "Bad request",
62
+ "SYS_005": "Request timed out, please check network and retry",
63
+ "SYS_006": "Service temporarily unavailable, please try again later",
64
+ "AUTH_004": "Access denied",
65
+ "AI_003": "AI service unavailable, please try again later",
66
+ "AI_004": "Too many requests, please try again later",
67
+ "ORCH_001": "Conversation not found",
68
+ "ORCH_003": "Conversation expired, please start a new one"
69
+ }
70
+ }
71
+
@@ -0,0 +1,26 @@
1
+ /**
2
+ * English translations - aggregates all namespaces
3
+ */
4
+
5
+ import common from './common.json';
6
+ import auth from './auth.json';
7
+ import chat from './chat.json';
8
+ import tasks from './tasks.json';
9
+ import learning from './learning.json';
10
+ import profile from './profile.json';
11
+ import errors from './errors.json';
12
+ import sidebar from './sidebar.json';
13
+
14
+ export const en = {
15
+ common,
16
+ auth,
17
+ chat,
18
+ tasks,
19
+ learning,
20
+ profile,
21
+ errors,
22
+ sidebar,
23
+ };
24
+
25
+ export default en;
26
+
@@ -0,0 +1,58 @@
1
+ {
2
+ "webview": {
3
+ "title": "Learning",
4
+ "loading": "Loading learning content...",
5
+ "loadFailed": "Unable to Load",
6
+ "loadFailedDesc": "Please check your internet connection and try again.",
7
+ "retry": "Retry",
8
+ "openExternal": "Open in Browser",
9
+ "close": "Close"
10
+ },
11
+ "lesson": {
12
+ "title": "Lesson",
13
+ "section": "Section {{current}} of {{total}}",
14
+ "next": "Next",
15
+ "previous": "Previous",
16
+ "startExercises": "Start Exercises"
17
+ },
18
+ "exercise": {
19
+ "title": "Exercise",
20
+ "question": "Question {{current}} of {{total}}",
21
+ "hint": "Show Hint",
22
+ "submit": "Submit",
23
+ "next": "Next Question",
24
+ "finish": "Finish"
25
+ },
26
+ "feedback": {
27
+ "correct": "Correct! 🎉",
28
+ "incorrect": "Not quite right",
29
+ "tryAgain": "Try again",
30
+ "showAnswer": "Show Answer",
31
+ "explanation": "Explanation"
32
+ },
33
+ "progress": {
34
+ "title": "Progress",
35
+ "questionsLeft": "{{count}} questions left",
36
+ "almostDone": "Almost done!",
37
+ "lastQuestion": "Last question!"
38
+ },
39
+ "celebration": {
40
+ "title": "Great job! 🌟",
41
+ "subtitle": "You completed all exercises",
42
+ "score": "Your score: {{score}}%",
43
+ "perfect": "Perfect score!",
44
+ "viewReport": "View Report",
45
+ "backToTasks": "Back to Tasks",
46
+ "shareResult": "Share Result"
47
+ },
48
+ "streak": {
49
+ "title": "{{count}} in a row! 🔥",
50
+ "keep": "Keep going!"
51
+ },
52
+ "difficulty": {
53
+ "easy": "Easy",
54
+ "medium": "Medium",
55
+ "challenge": "Challenge"
56
+ }
57
+ }
58
+
@@ -0,0 +1,134 @@
1
+ {
2
+ "title": "Profile",
3
+ "loading": "Loading...",
4
+ "user": {
5
+ "greeting": "Hello, {{name}}!",
6
+ "defaultName": "User",
7
+ "parent": "Parent",
8
+ "myAccount": "My Account",
9
+ "joinedOn": "Joined {{date}}",
10
+ "editName": "Edit Name",
11
+ "editNameTitle": "Edit Nickname",
12
+ "nickname": "Nickname",
13
+ "namePlaceholder": "Enter your name",
14
+ "nicknameHint": "This is the name displayed in the app",
15
+ "nameUpdated": "Name updated",
16
+ "updateFailed": "Update failed, please try again",
17
+ "saveSuccess": "Saved successfully",
18
+ "nicknameUpdated": "Nickname updated",
19
+ "save": "Save",
20
+ "saving": "Saving...",
21
+ "logout": "Log Out",
22
+ "logoutTitle": "Log Out",
23
+ "logoutConfirm": "Are you sure you want to log out?",
24
+ "logoutConfirmDesc": "You will need to log in again to continue using the app.",
25
+ "cancel": "Cancel",
26
+ "loggingOut": "Logging out...",
27
+ "pleaseEnterNickname": "Please enter a nickname"
28
+ },
29
+ "child": {
30
+ "title": "Child Profile",
31
+ "profile": "Child Profile",
32
+ "current": "Current",
33
+ "switch": "Switch",
34
+ "add": "Add",
35
+ "addChild": "Add Child",
36
+ "create": "Create Child Profile",
37
+ "createChild": "Create",
38
+ "creating": "Creating...",
39
+ "createSuccess": "Created successfully",
40
+ "createSuccessDesc": "Child profile has been created",
41
+ "createFailed": "Creation failed",
42
+ "addPrompt": "Tap to add a child profile",
43
+ "noProfileYet": "No child profile yet",
44
+ "addMoreHint": "You can add more child profiles in your profile",
45
+ "manage": "Manage",
46
+ "manageTitle": "Manage Child Profiles",
47
+ "edit": "Edit Profile",
48
+ "editChild": "Edit Child Profile",
49
+ "editTitle": "Edit Child Profile",
50
+ "delete": "Remove",
51
+ "deleting": "Removing...",
52
+ "deleteConfirm": "Remove {{name}}'s profile? Learning history will be preserved.",
53
+ "deleteConfirmTitle": "Confirm Removal",
54
+ "deleteConfirmDesc": "Are you sure you want to remove {{name}}'s profile? This action cannot be undone.",
55
+ "deleteConfirmName": "Are you sure you want to remove \"{{name}}\"'s profile?",
56
+ "deleteSuccess": "Profile removed",
57
+ "deleteFailed": "Failed to remove",
58
+ "setDefault": "Set as Default",
59
+ "setDefaultSuccess": "Set {{name}} as default",
60
+ "setDefaultFailed": "Failed to set default, please retry",
61
+ "switchSuccess": "Switched successfully",
62
+ "switchSuccessDesc": "Current child has been changed",
63
+ "switchFailed": "Failed to switch",
64
+ "noProfiles": "No child profiles yet",
65
+ "maxReached": "Maximum {{count}} child profiles allowed",
66
+ "taskCount": "{{count}} tasks",
67
+ "nickname": "Child's Nickname",
68
+ "nicknamePlaceholder": "Enter nickname",
69
+ "nicknameRequired": "Please enter the child's nickname",
70
+ "grade": "Grade",
71
+ "gradeLabel": "Grade",
72
+ "avatar": "Avatar",
73
+ "selectAvatar": "Select Avatar",
74
+ "save": "Save Changes",
75
+ "saveSuccess": "Saved successfully",
76
+ "saveFailed": "Failed to save",
77
+ "saving": "Saving...",
78
+ "updateSuccess": "Update successful",
79
+ "updateFailed": "Update failed",
80
+ "loading": "Loading..."
81
+ },
82
+ "grades": {
83
+ "P1": "Primary 1",
84
+ "P2": "Primary 2",
85
+ "P3": "Primary 3"
86
+ },
87
+ "stats": {
88
+ "title": "Learning Stats",
89
+ "tasks": "Tasks",
90
+ "total": "Total",
91
+ "completed": "Completed",
92
+ "days": "Days",
93
+ "learningDays": "Learning Days",
94
+ "tasksCompleted": "Tasks Completed",
95
+ "questionsAnswered": "Questions Answered",
96
+ "averageScore": "Average Score",
97
+ "streak": "Current Streak"
98
+ },
99
+ "settings": {
100
+ "title": "Settings",
101
+ "sound": "Sound Effects",
102
+ "music": "Background Music",
103
+ "notifications": "Notifications",
104
+ "help": "Help & Feedback",
105
+ "about": "About StudyJoyful",
106
+ "aboutTitle": "About StudyJoyful",
107
+ "version": "v{{version}}",
108
+ "versionLabel": "Version",
109
+ "poweredBy": "Powered by AquilaAI Lab",
110
+ "copyright": "© 2024 {{company}}",
111
+ "terms": "Terms of Service",
112
+ "privacy": "Privacy Policy"
113
+ },
114
+ "emailCopied": {
115
+ "title": "Email Copied",
116
+ "description": "Please contact us via email",
117
+ "ok": "OK"
118
+ },
119
+ "onboarding": {
120
+ "title": "Let's get to know your child",
121
+ "description": "We'll create learning content tailored to their grade and pace",
122
+ "nickname": "Child's Nickname",
123
+ "nicknamePlaceholder": "e.g. Lucas, Mia",
124
+ "nicknameRequired": "Please enter a nickname",
125
+ "grade": "Grade",
126
+ "avatar": "Choose Avatar",
127
+ "creating": "Creating...",
128
+ "startLearning": "Start Learning",
129
+ "createSuccess": "Created successfully!",
130
+ "createFailed": "Creation failed",
131
+ "footer": "You can modify this information anytime in your profile"
132
+ }
133
+ }
134
+
@@ -0,0 +1,64 @@
1
+ {
2
+ "menu": {
3
+ "home": "StudyJoyful",
4
+ "tasks": "My Tasks",
5
+ "admin": "Admin",
6
+ "settings": "Settings"
7
+ },
8
+ "status": {
9
+ "idle": "New",
10
+ "clarify_profile": "Gathering Info",
11
+ "clarify_content": "In Progress",
12
+ "ready": "Ready",
13
+ "generating": "Generating",
14
+ "complete": "Complete",
15
+ "reject": "Cancelled",
16
+ "error": "Error"
17
+ },
18
+ "statusShort": {
19
+ "idle": "New",
20
+ "clarify_profile": "Info",
21
+ "clarify_content": "Chat",
22
+ "ready": "Ready",
23
+ "generating": "Gen...",
24
+ "complete": "Done",
25
+ "reject": "Cancelled",
26
+ "error": "Error"
27
+ },
28
+ "time": {
29
+ "yesterday": "Yesterday",
30
+ "weekdays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
31
+ },
32
+ "history": {
33
+ "title": "Recent Conversations",
34
+ "empty": "No conversations yet",
35
+ "messages": "{{count}} messages",
36
+ "new": "New",
37
+ "newConversation": "New Conversation"
38
+ },
39
+ "actions": {
40
+ "refresh": "Refresh",
41
+ "retry": "Tap to retry",
42
+ "viewTask": "View Task",
43
+ "expand": "Expand sidebar",
44
+ "collapse": "Collapse sidebar",
45
+ "reload": "Reload",
46
+ "delete": "Delete"
47
+ },
48
+ "loading": "Loading...",
49
+ "error": {
50
+ "failed": "Failed to load"
51
+ },
52
+ "delete": {
53
+ "title": "Delete Conversation",
54
+ "description": "Delete \"{{title}}\"? This cannot be undone.",
55
+ "confirm": "Delete",
56
+ "deleting": "Deleting...",
57
+ "cancel": "Cancel",
58
+ "failed": "Delete failed"
59
+ },
60
+ "fallback": {
61
+ "brandSlogan": "Joyful Learning Starts Here",
62
+ "defaultUser": "User"
63
+ }
64
+ }
@@ -0,0 +1,109 @@
1
+ {
2
+ "title": "My Tasks",
3
+ "subtitle": "{{count}} learning tasks",
4
+ "loading": "Loading tasks...",
5
+ "loadingMore": "Loading more...",
6
+ "noMore": "No more tasks",
7
+ "error": "Failed to load tasks",
8
+ "retry": "Retry",
9
+ "empty": {
10
+ "title": "No learning tasks yet",
11
+ "titleFiltered": "No {{subject}} tasks yet",
12
+ "description": "Start a new conversation to create your first learning task!",
13
+ "action": "Create Task"
14
+ },
15
+ "card": {
16
+ "questions": "{{completed}}/{{total}} Questions",
17
+ "progress": "{{completed}}/{{total}} completed",
18
+ "notStarted": "Not started",
19
+ "inProgress": "In progress",
20
+ "completed": "Completed",
21
+ "viewReport": "View Report"
22
+ },
23
+ "preview": {
24
+ "title": "Content Preview",
25
+ "lessonCard": "Lesson Card",
26
+ "exercises": "Exercises",
27
+ "sections": "{{count}} sections",
28
+ "startLearning": "Start Learning",
29
+ "share": "Share Task",
30
+ "shareSuccess": "Link copied!",
31
+ "noShareLink": "No share link available",
32
+ "report": "View Report",
33
+ "loading": "Loading...",
34
+ "loadFailed": "Failed to Load",
35
+ "retry": "Retry"
36
+ },
37
+ "report": {
38
+ "title": "Learning Report",
39
+ "loading": "Loading report...",
40
+ "loadFailed": "Failed to Load",
41
+ "loadFailedDesc": "Unable to load report",
42
+ "back": "Back",
43
+ "tryAgain": "Try Again",
44
+ "score": "Score",
45
+ "totalScore": "Total Score",
46
+ "correct": "Correct",
47
+ "wrong": "Wrong",
48
+ "correctCount": "{{count}} correct",
49
+ "wrongCount": "{{count}} wrong",
50
+ "firstTryCorrect": "First Try Correct",
51
+ "totalRetries": "Total Retries",
52
+ "avgRetries": "avg {{count}}/question",
53
+ "totalQuestions": "{{count}} total",
54
+ "questionsCount": "{{count}} questions",
55
+ "questionDetails": "Question Details",
56
+ "retryCount": "Retried {{count}} times",
57
+ "correctAnswer": "Correct Answer:",
58
+ "suggestion": "Learning Suggestion",
59
+ "empty": {
60
+ "title": "No Results Yet",
61
+ "description": "Your child hasn't completed this task yet. Results will appear here after completion."
62
+ },
63
+ "suggestions": {
64
+ "excellent": "Excellent! Your child has mastered \"{{topic}}\" well. Ready for more challenging content!",
65
+ "good": "Good job! Your child understands \"{{topic}}\" but has room to improve. Consider reviewing mistakes.",
66
+ "needsPractice": "Your child may need more practice with \"{{topic}}\". Try reviewing the lesson first, then practice again.",
67
+ "excellentTitle": "🎉 Excellent!",
68
+ "excellentContent": "Your child has mastered \"{{topic}}\" very well, with {{count}} questions passed on first try! Ready for more challenging content.",
69
+ "goodTitle": "👍 Good job!",
70
+ "goodContent": "Your child has a good grasp of \"{{topic}}\". {{count}} questions need review - consider practicing those.",
71
+ "keepGoingTitle": "💪 Keep going!",
72
+ "keepGoingContent": "Your child is learning \"{{topic}}\". Try reviewing the lesson card first, then practice the {{count}} questions that need work.",
73
+ "needMoreTitle": "📚 More practice needed",
74
+ "needMoreContent": "\"{{topic}}\" is still new to your child. Start by carefully studying the lesson card, then try the exercises again."
75
+ },
76
+ "performance": {
77
+ "excellent": "Excellent",
78
+ "good": "Good",
79
+ "keepGoing": "Keep Going"
80
+ },
81
+ "status": {
82
+ "firstTryPass": "First try pass",
83
+ "passedAfterRetry": "Passed after {{count}} retries",
84
+ "needsReview": "Needs review"
85
+ },
86
+ "stats": {
87
+ "masteryRate": "First Try Rate",
88
+ "total": "Total",
89
+ "totalQuestions": "{{count}} total",
90
+ "firstTryCount": "{{count}} first try",
91
+ "needReviewCount": "{{count}} need review",
92
+ "firstTryDescription": "Passed on first attempt",
93
+ "needReviewDescription": "Retried or incorrect",
94
+ "allFirstTry": "All passed on first try!"
95
+ }
96
+ },
97
+ "filter": {
98
+ "all": "All",
99
+ "math": "Math",
100
+ "english": "English",
101
+ "inProgress": "In Progress",
102
+ "completed": "Completed"
103
+ },
104
+ "sort": {
105
+ "newest": "Newest",
106
+ "oldest": "Oldest"
107
+ }
108
+ }
109
+
@@ -0,0 +1,14 @@
1
+ /**
2
+ * All locales aggregated for i18next resources
3
+ */
4
+
5
+ import { en } from './en';
6
+
7
+ export const resources = {
8
+ en: en,
9
+ } as const;
10
+
11
+ export { en };
12
+
13
+ export default resources;
14
+
package/src/index.ts ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @studyjoyful/shared
3
+ *
4
+ * Shared code for StudyJoyful Web and App.
5
+ *
6
+ * Modules:
7
+ * - i18n: Internationalization (translations, resources)
8
+ * - constants: Shared constants (examples, etc.)
9
+ * - types: Common type definitions
10
+ * - utils: Utility functions
11
+ */
12
+
13
+ // i18n exports
14
+ export {
15
+ resources,
16
+ namespaces,
17
+ defaultNS,
18
+ defaultLanguage,
19
+ en,
20
+ type Namespace,
21
+ } from './i18n';
22
+
23
+ // Constants exports
24
+ export {
25
+ EXAMPLES_BY_GRADE,
26
+ getAllExamples,
27
+ getExamplesByGrade,
28
+ type Example,
29
+ } from './constants';
30
+
31
+ // Types exports
32
+ export {
33
+ type ChildGrade,
34
+ type Subject,
35
+ type AvatarId,
36
+ type ChildProfile,
37
+ ALL_GRADES,
38
+ ALL_SUBJECTS,
39
+ GRADE_LABELS,
40
+ type PaginationParams,
41
+ type PaginationMeta,
42
+ } from './types';
43
+
44
+ // Utils exports
45
+ export { randomSelect, shuffle } from './utils';
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Child Profile Types
3
+ *
4
+ * Types related to child profiles and grades.
5
+ * Used in both Web and App for consistent typing.
6
+ */
7
+
8
+ /**
9
+ * Singapore primary school grades (P1-P6)
10
+ * Product currently focuses on P1-P3 (ages 6-9)
11
+ */
12
+ export type ChildGrade = 'P1' | 'P2' | 'P3';
13
+
14
+ /**
15
+ * Available subjects
16
+ * Product focuses on Math and English only
17
+ */
18
+ export type Subject = 'Math' | 'English';
19
+
20
+ /**
21
+ * Avatar identifiers (1-20)
22
+ */
23
+ export type AvatarId = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20;
24
+
25
+ /**
26
+ * Child profile interface
27
+ */
28
+ export interface ChildProfile {
29
+ id: string;
30
+ name: string;
31
+ grade: ChildGrade;
32
+ avatarId: AvatarId;
33
+ isDefault: boolean;
34
+ createdAt: string;
35
+ updatedAt: string;
36
+ }
37
+
38
+ /**
39
+ * All supported grades for iteration
40
+ */
41
+ export const ALL_GRADES: ChildGrade[] = ['P1', 'P2', 'P3'];
42
+
43
+ /**
44
+ * All supported subjects for iteration
45
+ */
46
+ export const ALL_SUBJECTS: Subject[] = ['Math', 'English'];
47
+
48
+ /**
49
+ * Grade display labels
50
+ */
51
+ export const GRADE_LABELS: Record<ChildGrade, string> = {
52
+ P1: 'Primary 1',
53
+ P2: 'Primary 2',
54
+ P3: 'Primary 3',
55
+ };
56
+
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Common Types
3
+ *
4
+ * General-purpose type definitions.
5
+ */
6
+
7
+ /**
8
+ * Generic pagination parameters
9
+ */
10
+ export interface PaginationParams {
11
+ page?: number;
12
+ pageSize?: number;
13
+ sortBy?: string;
14
+ sortOrder?: 'asc' | 'desc';
15
+ }
16
+
17
+ /**
18
+ * Generic pagination metadata
19
+ */
20
+ export interface PaginationMeta {
21
+ total: number;
22
+ page: number;
23
+ pageSize: number;
24
+ totalPages: number;
25
+ hasNextPage: boolean;
26
+ hasPrevPage: boolean;
27
+ }
28
+
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared Types
3
+ *
4
+ * Common type definitions used across Web and App.
5
+ */
6
+
7
+ export * from './child';
8
+ export * from './common';
9
+
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared Utilities
3
+ *
4
+ * Common utility functions used across Web and App.
5
+ */
6
+
7
+ export * from './random';
8
+
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Random Utilities
3
+ *
4
+ * Functions for random selection and shuffling.
5
+ */
6
+
7
+ /**
8
+ * Randomly select n elements from array
9
+ *
10
+ * @param arr - Source array
11
+ * @param count - Number of elements to select
12
+ * @returns New array with randomly selected elements
13
+ */
14
+ export function randomSelect<T>(arr: T[], count: number): T[] {
15
+ if (count >= arr.length) return [...arr];
16
+ const shuffled = [...arr].sort(() => Math.random() - 0.5);
17
+ return shuffled.slice(0, count);
18
+ }
19
+
20
+ /**
21
+ * Shuffle array (Fisher-Yates algorithm)
22
+ *
23
+ * @param arr - Source array
24
+ * @returns New shuffled array
25
+ */
26
+ export function shuffle<T>(arr: T[]): T[] {
27
+ const result = [...arr];
28
+ for (let i = result.length - 1; i > 0; i--) {
29
+ const j = Math.floor(Math.random() * (i + 1));
30
+ [result[i], result[j]] = [result[j], result[i]];
31
+ }
32
+ return result;
33
+ }
34
+