diff --git a/.cursor/rules/animation-guidelines.mdc b/.cursor/rules/animation-guidelines.mdc index c89d21f8e..ab273a8a5 100644 --- a/.cursor/rules/animation-guidelines.mdc +++ b/.cursor/rules/animation-guidelines.mdc @@ -61,23 +61,28 @@ The goal is to make complex products accessible to newcomers without sacrificing A system of components housed within trays that expand, contract, and adapt in response to user actions: **Tray Initiation Rules:** + - Trays are initiated by the user (tapping buttons, icons, or opening notifications) - They can appear standalone on top of any content, or emerge from within other components like buttons **Height Variation Rule:** + - Each subsequent tray varies in height to make progression unmistakably clear - This constraint may require rewriting content or tweaking designs to make transitions apparent **Single Focus Rule:** + - Each tray is dedicated to a singular piece of content (like educational text) or a primary action (like completing a checklist) - Every tray has a title capturing its function and an icon for dismissal/navigation **Context Preservation:** + - Unlike full screen transitions that displace users, trays overlay content directly onto the current interface - Users aren't veering off course—they're diving deeper into their current context - Contextual continuity keeps flows feeling integrated rather than disjointed **When to Use Trays vs Full Screens:** + - Use trays for transient actions that don't need permanent display - Especially helpful for confirmation steps and warnings that appear at the right time - Trays can serve as starting points for elaborate flows that transition to full screen @@ -118,6 +123,7 @@ While speed is important, applying motion thoughtfully can enhance clarity and f **If a component is visible and will persist in the next phase, it should remain consistent.** Components should "travel" between screens rather than disappear and reappear. Examples: + - Wallet cards move seamlessly between screens - Empty states keep unchanged text constant when only a portion needs updating - The same element animates into its new position rather than fading out/in @@ -125,6 +131,7 @@ Examples: ### Connected State Transitions Create interactions where: + - Trays morph into full screen views - Buttons glide across trays - Buttons morph into trays and back again @@ -142,6 +149,7 @@ For actions where understanding is crucial (like sending money): ### The Cost of Removing Fluidity Without fluid animations: + - The sense of connection is lost - Contextual continuity disappears - Actions feel like "digital whiplash" @@ -162,6 +170,7 @@ Delight is about creating moments that resonate on a personal level—making sof ### The Foundation of Delight Before adding delightful moments: + - Achieve consistent polish everywhere first - Users notice when parts of an app are less polished - Every part of the app deserves the same holistic design approach @@ -173,11 +182,11 @@ Before adding delightful moments: The potential for delight increases as feature usage frequency decreases: -| Feature Frequency | Delight Strategy | -|------------------|------------------| +| Feature Frequency | Delight Strategy | +| -------------------------- | ------------------------------------------------------------ | | Daily use (sending tokens) | Focus on small, efficient touches that don't become tiresome | -| Occasional use (settings) | Add satisfying interactions that reward exploration | -| Rare use (wallet setup) | Create memorable, celebration-worthy moments | +| Occasional use (settings) | Add satisfying interactions that reward exploration | +| Rare use (wallet setup) | Create memorable, celebration-worthy moments | **Why this works:** The "specialness of a moment" generally decreases with repeated encounters. Rare features benefit most from delightful surprises. @@ -188,6 +197,7 @@ The potential for delight increases as feature usage frequency decreases: - The discovery process itself creates delight **Examples:** + - QR code screen: Tapping triggers a gentle ripple effect - Swiping across QR code reveals a sequin-like transformation - Entering an amount exceeding balance triggers a playful easter egg @@ -197,31 +207,35 @@ The potential for delight increases as feature usage frequency decreases: Match delight intensity to feature frequency: **High-frequency features (daily use):** + - Commas visually shift place-to-place when inputting numbers - Small, efficient touches that don't become annoying **Medium-frequency features:** + - Drag-and-drop with attractive stacking animations - Satisfying rather than tedious interactions **Low-frequency features (rare use):** + - Wallet setup: Interactive animation marking the significant occasion - Backup completion: Confetti fills the screen as reward - Trash items: Visually tumble into a skeuomorphic trash can with sound effects ### Specific Delight Patterns -| Feature | Delightful Touch | -|---------|------------------| -| First-time browser | Animated arrow guides toward creating a new tab | -| Reordering items | Smooth drag-and-drop with stacking animations | -| Stealth mode | Gentle shimmer effect signals hidden-but-updating values | -| Price charts | Arrow flips direction alongside changing numbers | -| Security tasks | Confetti rewards completion of essential tasks | +| Feature | Delightful Touch | +| ------------------ | -------------------------------------------------------- | +| First-time browser | Animated arrow guides toward creating a new tab | +| Reordering items | Smooth drag-and-drop with stacking animations | +| Stealth mode | Gentle shimmer effect signals hidden-but-updating values | +| Price charts | Arrow flips direction alongside changing numbers | +| Security tasks | Confetti rewards completion of essential tasks | ### The Purpose of Delight Delightful moments are not just entertainment—they: + - Value and reward the user's time and emotional investment - Transform mundane interactions into memorable ones - Create a familiar, friendly companion rather than just a tool @@ -245,21 +259,7 @@ Delightful moments are not just entertainment—they: - **Consider Ease-In-Out:** Appropriate for elements that move and scale but stay on the screen. - **Avoid Linear:** Linear animations feel unnatural and lack energy; avoid them in 99% of cases. -Use the following custom easing curves for a polished feel: - -```css -/* Standard ease-out - good default for most animations */ ---ease-out: cubic-bezier(0.16, 1, 0.3, 1); - -/* Smooth ease-in-out for symmetrical animations */ ---ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); - -/* Snappy ease for quick feedback */ ---ease-snappy: cubic-bezier(0.2, 0, 0, 1); - -/* Spring-like bounce */ ---ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); -``` +Use these Custom easing curves for a polished feel exported from `@constants/animations.ts` - import `easeOut`, `easeInOut`, `easeSnappy`, or `easeSpring` for use with React Native Reanimated animations. ### Button Press Feedback @@ -426,19 +426,16 @@ For interactive elements, spring physics feel more natural than duration-based a ### Accessibility -- **Respect User Preferences:** Use media queries like `prefers-reduced-motion` to disable or reduce animations for users who prefer it. +- **Respect User Preferences:** Use React Native Reanimated's reduced motion functionality to respect system accessibility settings. All animations should respect the user's reduced motion preferences by default. -```css -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } -} -``` +React Native Reanimated provides: + +- `ReduceMotion` enum (`System`, `Always`, `Never`) for configuring animation behavior +- `reduceMotion` option in animation functions (`withTiming`, `withSpring`, `withDelay`, etc.) +- `.reduceMotion()` method on layout animations (entering/exiting animations) +- `useReducedMotion()` hook for conditional animation logic + +**Reference:** See the [React Native Reanimated Accessibility Guide](https://docs.swmansion.com/react-native-reanimated/docs/guides/accessibility/) for complete documentation, examples, and implementation details. --- @@ -463,6 +460,7 @@ For interactive elements, spring physics feel more natural than duration-based a ### Delight Audit When implementing a feature, ask: + 1. How frequently will users encounter this? 2. What's the appropriate intensity of delight? 3. Is there an opportunity for surprise/discovery? @@ -499,6 +497,7 @@ When implementing a feature, ask: ## Trade-offs and Commitment Creating this level of experience requires: + - Conscious trade-offs in development speed - Obsessive attention to detail - Deep understanding of app navigation architecture diff --git a/.cursor/rules/creating-components.mdc b/.cursor/rules/creating-components.mdc index 95e4704f7..1b3ec8174 100644 --- a/.cursor/rules/creating-components.mdc +++ b/.cursor/rules/creating-components.mdc @@ -189,6 +189,25 @@ While React Compiler can automatically memoize components, it has limitations an - **ESLint Disable Comments**: Never use `// eslint-disable-next-line` or similar ESLint disable comments in your code. These comments prevent React Compiler from properly analyzing and optimizing your code. If ESLint flags an issue, fix the underlying problem rather than suppressing the warning. For legitimate cases where a dependency should be excluded (like stable functions or refs), refactor the code to make the stability explicit rather than disabling the exhaustive-deps rule. +- **React Native Reanimated Shared Values**: When working with React Native Reanimated's `useSharedValue`, use the `.get()` and `.set()` methods instead of accessing `.value` directly. This is the React Compiler-compliant API that avoids the need for refs or workarounds. Example: + +```tsx +function App() { + const sv = useSharedValue(100); + + const animatedStyle = useAnimatedStyle(() => { + 'worklet'; + return { width: sv.get() * 100 }; + }); + + const handlePress = () => { + sv.set(withTiming(200, { duration: 300 })); + }; +} +``` + +This eliminates the need for refs (`useRef`) and `useEffect` when modifying shared values in inline handlers, as React Compiler treats direct `.value` access as immutable mutations. + ### Types and Props API - **TypeScript**: Ship with comprehensive types for maximum safety and autocomplete diff --git a/.eas/workflows/deploy-to-preview.yml b/.eas/workflows/deploy-to-preview.yml index 59fb0a2c3..9c1718d9c 100644 --- a/.eas/workflows/deploy-to-preview.yml +++ b/.eas/workflows/deploy-to-preview.yml @@ -21,6 +21,7 @@ on: - '!deno.json' - '!.cursor/**' - '!.vscode/**' + - '!.maestro/**' jobs: fingerprint: diff --git a/.eas/workflows/deploy-to-production.yml b/.eas/workflows/deploy-to-production.yml index 13684044e..8deb93f4a 100644 --- a/.eas/workflows/deploy-to-production.yml +++ b/.eas/workflows/deploy-to-production.yml @@ -21,6 +21,7 @@ on: - '!deno.json' - '!.cursor/**' - '!.vscode/**' + - '!.maestro/**' jobs: fingerprint: diff --git a/.eas/workflows/run-preview-tests.yml b/.eas/workflows/run-preview-tests.yml index 1b7b4816d..15f3bf863 100644 --- a/.eas/workflows/run-preview-tests.yml +++ b/.eas/workflows/run-preview-tests.yml @@ -40,6 +40,16 @@ jobs: profile: preview platform: android + ios_get_build: + name: Check for existing ios build + needs: [fingerprint] + type: get-build + environment: preview + params: + fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }} + profile: preview-simulator + platform: ios + android_repack: name: Repack Android needs: [android_get_build] @@ -61,6 +71,27 @@ jobs: platform: android profile: preview + ios_repack: + name: Repack iOS + needs: [ios_get_build] + if: ${{ needs.ios_get_build.outputs.build_id }} + type: repack + environment: preview + runs_on: macos-medium + params: + build_id: ${{ needs.ios_get_build.outputs.build_id }} + + ios_build: + name: Build iOS + needs: [ios_get_build] + if: ${{ !needs.ios_get_build.outputs.build_id }} + type: build + environment: preview + runs_on: macos-medium + params: + platform: ios + profile: preview-simulator + android_maestro: name: Run Android Maestro Tests after: [android_repack, android_build] @@ -71,44 +102,19 @@ jobs: params: build_id: ${{ needs.android_repack.outputs.build_id || needs.android_build.outputs.build_id }} record_screen: true + device_identifier: 'pixel_6' android_system_image_package: 'system-images;android-31;default;x86_64' - flow_path: ['maestro/register.yaml', 'maestro/sign-in.yaml'] - - # ios_get_build: - # name: Check for existing ios build - # needs: [fingerprint] - # type: get-build - # environment: preview - # params: - # fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }} - # profile: preview-simulator - - # ios_repack: - # name: Repack iOS - # needs: [ios_get_build] - # if: ${{ needs.ios_get_build.outputs.build_id }} - # type: repack - # environment: preview - # params: - # build_id: ${{ needs.ios_get_build.outputs.build_id }} - - # ios_build: - # name: Build iOS - # needs: [ios_get_build] - # if: ${{ !needs.ios_get_build.outputs.build_id }} - # type: build - # environment: preview - # params: - # platform: ios - # profile: preview-simulator + flow_path: ['.maestro/flows/register.yaml', '.maestro/flows/sign-in.yaml'] - # ios_maestro: - # name: Run iOS Maestro Tests - # after: [ios_repack, ios_build] - # type: maestro - # environment: preview - # image: latest - # params: - # build_id: ${{ needs.ios_repack.outputs.build_id || needs.ios_build.outputs.build_id }} - # flow_path: ['maestro.yaml'] - # flow_path: ['maestro.yaml'] + ios_maestro: + name: Run iOS Maestro Tests + after: [ios_repack, ios_build] + type: maestro + environment: preview + image: latest + runs_on: macos-medium + params: + record_screen: true + device_identifier: 'iPhone 16e' + build_id: ${{ needs.ios_repack.outputs.build_id || needs.ios_build.outputs.build_id }} + flow_path: ['.maestro/flows/register.yaml', '.maestro/flows/sign-in.yaml'] diff --git a/.eas/workflows/run-production-tests.yml b/.eas/workflows/run-production-tests.yml index f2fa13c48..7183b443a 100644 --- a/.eas/workflows/run-production-tests.yml +++ b/.eas/workflows/run-production-tests.yml @@ -40,6 +40,16 @@ jobs: profile: production-simulator platform: android + ios_get_build: + name: Check for existing ios build + needs: [fingerprint] + type: get-build + environment: production + params: + fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }} + profile: production-simulator + platform: ios + android_repack: name: Repack Android needs: [android_get_build] @@ -61,6 +71,27 @@ jobs: platform: android profile: production-simulator + ios_repack: + name: Repack iOS + needs: [ios_get_build] + if: ${{ needs.ios_get_build.outputs.build_id }} + type: repack + environment: production + runs_on: macos-medium + params: + build_id: ${{ needs.ios_get_build.outputs.build_id }} + + ios_build: + name: Build iOS + needs: [ios_get_build] + if: ${{ !needs.ios_get_build.outputs.build_id }} + type: build + environment: production + runs_on: macos-medium + params: + platform: ios + profile: production-simulator + android_maestro: name: Run Android Maestro Tests after: [android_repack, android_build] @@ -72,43 +103,16 @@ jobs: build_id: ${{ needs.android_repack.outputs.build_id || needs.android_build.outputs.build_id }} record_screen: true android_system_image_package: 'system-images;android-31;default;x86_64' - flow_path: ['maestro/register.yaml', 'maestro/sign-in.yaml'] - - # ios_get_build: - # name: Check for existing ios build - # needs: [fingerprint] - # type: get-build - # environment: production - # params: - # fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }} - # profile: production-simulator - - # ios_repack: - # name: Repack iOS - # needs: [ios_get_build] - # if: ${{ needs.ios_get_build.outputs.build_id }} - # type: repack - # environment: production - # params: - # build_id: ${{ needs.ios_get_build.outputs.build_id }} - - # ios_build: - # name: Build iOS - # needs: [ios_get_build] - # if: ${{ !needs.ios_get_build.outputs.build_id }} - # type: build - # environment: production - # params: - # platform: ios - # profile: production-simulator + flow_path: ['.maestro/flows/register.yaml', '.maestro/flows/sign-in.yaml'] - # ios_maestro: - # name: Run iOS Maestro Tests - # after: [ios_repack, ios_build] - # type: maestro - # environment: production - # image: latest - # params: - # build_id: ${{ needs.ios_repack.outputs.build_id || needs.ios_build.outputs.build_id }} - # flow_path: ['maestro.yaml'] - # flow_path: ['maestro.yaml'] + ios_maestro: + name: Run iOS Maestro Tests + after: [ios_repack, ios_build] + type: maestro + environment: production + image: latest + runs_on: macos-medium + params: + record_screen: true + build_id: ${{ needs.ios_repack.outputs.build_id || needs.ios_build.outputs.build_id }} + flow_path: ['.maestro/flows/register.yaml', '.maestro/flows/sign-in.yaml'] diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..93e1febb2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{bat,cmd,ps1}] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1ec875276 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text=auto eol=lf + +# Keep Windows-native script endings for better local tooling compatibility. +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf diff --git a/.github/workflows/check-native-build.yml b/.github/workflows/check-native-build.yml index ce2bc8e9d..698857895 100644 --- a/.github/workflows/check-native-build.yml +++ b/.github/workflows/check-native-build.yml @@ -78,6 +78,12 @@ jobs: } } + - name: Write fingerprint diff to file + if: github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'dev' + run: | + echo '${{ steps.fingerprint.outputs.fingerprint-diff }}' > fingerprint-diff-raw.json + shell: bash + - name: Check if fingerprint compatibility changed if: github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'dev' id: check-compatibility-change @@ -85,7 +91,12 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const fingerprintDiff = ${{ steps.fingerprint.outputs.fingerprint-diff }}; + const fs = require('fs'); + // Read fingerprint diff from file to avoid environment variable size limits + let fingerprintDiff = '[]'; + if (fs.existsSync('fingerprint-diff-raw.json')) { + fingerprintDiff = fs.readFileSync('fingerprint-diff-raw.json', 'utf8').trim(); + } // Handle fingerprint diff - it's a JSON string, check if it's an empty array const trimmedDiff = String(fingerprintDiff).trim(); // Empty array means no changes (compatible), non-empty means changes @@ -139,7 +150,7 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const message = `⚠️ Native Build Required - See the comment below for details.`; + const message = '⚠️ Native Build Required - See the comment below for details.'; await github.rest.pulls.createReview({ owner: context.repo.owner, @@ -156,39 +167,169 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const fingerprintDiff = ${{ steps.fingerprint.outputs.fingerprint-diff }}; - const prettifiedDiff = JSON.stringify(fingerprintDiff, null, 2); - core.setOutput('prettified_diff', prettifiedDiff); + const fs = require('fs'); + // Read from file to avoid environment variable size limits + let fingerprintDiffStr = '[]'; + if (fs.existsSync('fingerprint-diff-raw.json')) { + fingerprintDiffStr = fs.readFileSync('fingerprint-diff-raw.json', 'utf8').trim(); + } + // Parse the JSON string if it's valid JSON, otherwise use as-is + let fingerprintDiff; + try { + fingerprintDiff = JSON.parse(fingerprintDiffStr); + } catch (e) { + fingerprintDiff = fingerprintDiffStr; + } + + // Format as GitHub code diff + function formatSource(source) { + if (!source) return ''; + const parts = []; + if (source.type === 'file' || source.type === 'dir') { + parts.push(`Path: ${source.filePath}`); + } else if (source.type === 'contents') { + parts.push(`ID: ${source.id}`); + } + if (source.hash) { + parts.push(`Hash: ${source.hash}`); + } + if (source.reasons && source.reasons.length > 0) { + parts.push(`Reasons: ${source.reasons.join(', ')}`); + } + return parts.join(' | '); + } + + function formatDiffAsCode(diff) { + if (!Array.isArray(diff) || diff.length === 0) { + return 'No changes detected.'; + } + + const lines = []; + diff.forEach((item, index) => { + if (item.op === 'added') { + lines.push(`+ Added: ${formatSource(item.addedSource)}`); + } else if (item.op === 'removed') { + lines.push(`- Removed: ${formatSource(item.removedSource)}`); + } else if (item.op === 'changed') { + lines.push(`- Changed (before): ${formatSource(item.beforeSource)}`); + lines.push(`+ Changed (after): ${formatSource(item.afterSource)}`); + } + // Add separator between items (except last) + if (index < diff.length - 1) { + lines.push(''); + } + }); + + return lines.join('\n'); + } + + const codeDiff = formatDiffAsCode(fingerprintDiff); + // Write to file to avoid output size limits + fs.writeFileSync('fingerprint-diff-formatted.txt', codeDiff); + // Also keep JSON for reference + fs.writeFileSync('fingerprint-diff.json', JSON.stringify(fingerprintDiff, null, 2)); + // Set a flag output instead of the full diff + core.setOutput('has_diff_file', 'true'); - name: Comment on PR if fingerprint changed if: github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'dev' && steps.check-compatibility-change.outputs.has_changes == 'true' - uses: thollander/actions-comment-pull-request@v3 + uses: actions/github-script@v7 + env: + HAS_DIFF_FILE: ${{ steps.format-diff.outputs.has_diff_file }} with: - comment-tag: fingerprint-check - message: | - ⚠️ Native Build Required - Manual Approval Needed - - This PR requires a native build because the fingerprint has changed compared to the base branch. - -
- Fingerprint diff - - ```json - ${{ steps.format-diff.outputs.prettified_diff }} - ``` + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + let codeDiff = 'No changes detected.'; + let jsonDiff = '[]'; + + if (process.env.HAS_DIFF_FILE === 'true') { + if (fs.existsSync('fingerprint-diff-formatted.txt')) { + codeDiff = fs.readFileSync('fingerprint-diff-formatted.txt', 'utf8'); + } + if (fs.existsSync('fingerprint-diff.json')) { + jsonDiff = fs.readFileSync('fingerprint-diff.json', 'utf8'); + } + } + + const message = '\n' + + '⚠️ Native Build Required - Manual Approval Needed\n\n' + + 'This PR requires a native build because the fingerprint has changed compared to the base branch.\n\n' + + '
\n' + + '📋 Fingerprint diff\n\n' + + '```diff\n' + + codeDiff + '\n' + + '```\n\n' + + '
\n\n' + + '
\n' + + '🔍 Full JSON diff (for debugging)\n\n' + + '```json\n' + + jsonDiff + '\n' + + '```\n\n' + + '
\n\n' + + 'Action Required: After confirming with the team, another team member must approve this PR to proceed with merging.\n\n' + + '👉 [**Approve this PR**](' + context.serverUrl + '/' + context.repo.owner + '/' + context.repo.repo + '/pull/' + context.issue.number + '/files) - Click "Review changes/Submit review" → "Approve" to dismiss this review and allow merging.'; -
+ // Find existing comment with fingerprint-check marker + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); - Action Required: After confirming with the team, another team member must approve this PR to proceed with merging. + const existingComment = comments.find( + comment => comment.user.type === 'Bot' && + comment.user.login === 'github-actions[bot]' && + comment.body.includes('') + ); - 👉 [**Approve this PR**](${{ github.server_url }}/${{ github.repository }}/pull/${{ github.event.pull_request.number }}/files) - Click "Review changes/Submit review" → "Approve" to dismiss this review and allow merging. + if (existingComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body: message + }); + console.log('✅ Updated fingerprint comment'); + } else { + // Create new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: message + }); + console.log('✅ Created fingerprint comment'); + } - name: Remove comment if fingerprint compatible if: github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'dev' && steps.check-compatibility-change.outputs.has_changes == 'false' - uses: thollander/actions-comment-pull-request@v3 + uses: actions/github-script@v7 with: - comment-tag: fingerprint-check - mode: delete + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + // Find and delete comments with the fingerprint-check marker + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const fingerprintComment = comments.find( + comment => comment.user.type === 'Bot' && + comment.user.login === 'github-actions[bot]' && + comment.body.includes('') + ); + + if (fingerprintComment) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: fingerprintComment.id, + }); + console.log('✅ Removed fingerprint comment'); + } dismiss-bot-review-on-approval: if: github.event_name == 'pull_request_review' && github.event.review.state == 'approved' && github.event.pull_request.base.ref == 'dev' diff --git a/.gitignore b/.gitignore index b3378a05e..8d6575717 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,8 @@ npm-debug.* *.orig.* web-build/ llm_supp_files/ -android/ -ios/ +/android +/ios .env .env.* !.env.*.example @@ -43,7 +43,6 @@ langquest types.xlsx *.vsix llm_supp_files -/docs /.venv # include all version db files (1.0.db, 2.0.db, etc.) diff --git a/maestro/api.js b/.maestro/api.js similarity index 70% rename from maestro/api.js rename to .maestro/api.js index 430b26463..503e64b55 100644 --- a/maestro/api.js +++ b/.maestro/api.js @@ -132,7 +132,83 @@ function generatePasswordResetLink(email) { return resetLink; } +function deleteProject(projectName) { + // Validate projectName is defined and is a non-empty string + if ( + !projectName || + typeof projectName !== 'string' || + projectName.trim() === '' + ) { + throw new Error( + 'Project name is required and must be a non-empty string. Received: ' + + projectName + ); + } + + console.log('Deleting project with name:', projectName); + + // First, get the project by name using Supabase REST API + const getProjectResponse = http.get( + supabaseUrl + + '/rest/v1/project?name=eq.' + + encodeURIComponent(projectName) + + '&select=id', + { + headers: { + Authorization: 'Bearer ' + serviceRoleKey, + apikey: serviceRoleKey, + 'Content-Type': 'application/json', + Prefer: 'return=representation' + } + } + ); + + if (getProjectResponse.status !== 200) { + throw new Error( + 'Failed to query project: ' + + getProjectResponse.status + + ' ' + + getProjectResponse.body + ); + } + + // Parse the response to get the project ID + const projects = JSON.parse(getProjectResponse.body); + if (!projects || projects.length === 0) { + throw new Error('Project not found with name: ' + projectName); + } + + const projectId = projects[0].id; + console.log('Found project ID:', projectId); + + // Delete the project by ID + const deleteResponse = http.delete( + supabaseUrl + '/rest/v1/project?id=eq.' + projectId, + { + headers: { + Authorization: 'Bearer ' + serviceRoleKey, + apikey: serviceRoleKey, + 'Content-Type': 'application/json', + Prefer: 'return=representation' + } + } + ); + + if (deleteResponse.status !== 200 && deleteResponse.status !== 204) { + throw new Error( + 'Failed to delete project: ' + + deleteResponse.status + + ' ' + + deleteResponse.body + ); + } + + console.log('Successfully deleted project:', projectName); + return deleteResponse; +} + output.api = { deleteUser, - generatePasswordResetLink + generatePasswordResetLink, + deleteProject }; diff --git a/.maestro/flows/_ios-toggle-autofill-passwords.yaml b/.maestro/flows/_ios-toggle-autofill-passwords.yaml new file mode 100644 index 000000000..a3eed6877 --- /dev/null +++ b/.maestro/flows/_ios-toggle-autofill-passwords.yaml @@ -0,0 +1,33 @@ +appId: ${MAESTRO_APP_ID} +--- +# iOS-only subflow to toggle AutoFill Passwords in Settings +# This should only be called from iOS test flows +- launchApp: + appId: 'com.apple.Preferences' +- scrollUntilVisible: + element: Apps +- tapOn: Apps +- scrollUntilVisible: + element: Passwords +- tapOn: Passwords +- scrollUntilVisible: + element: View AutoFill Settings +- tapOn: View AutoFill Settings +- runFlow: + when: + visible: + text: '1' + rightOf: AutoFill Passwords and Passkeys + commands: + - tapOn: + text: '1' + rightOf: AutoFill Passwords and Passkeys +- runFlow: + when: + visible: + text: '1' + rightOf: Suggest Strong Passwords + commands: + - tapOn: + text: '1' + rightOf: Suggest Strong Passwords diff --git a/.maestro/flows/_launch-and-onboard.yaml b/.maestro/flows/_launch-and-onboard.yaml new file mode 100644 index 000000000..209ced421 --- /dev/null +++ b/.maestro/flows/_launch-and-onboard.yaml @@ -0,0 +1,16 @@ +appId: ${MAESTRO_APP_ID} +--- +- launchApp: + clearState: true +- assertVisible: Terms & Privacy +- scrollUntilVisible: + element: I agree +- tapOn: + text: I agree + waitToSettleTimeoutMs: 0 # stop waiting for onboarding animation +- assertVisible: + text: Every language. Every culture. + waitToSettleTimeoutMs: 0 # stop waiting for onboarding animation +- tapOn: + id: onboarding-close-button + waitToSettleTimeoutMs: 0 # stop waiting for onboarding animation diff --git a/.maestro/flows/create-records.yaml b/.maestro/flows/create-records.yaml new file mode 100644 index 000000000..734db49f4 --- /dev/null +++ b/.maestro/flows/create-records.yaml @@ -0,0 +1,43 @@ +appId: ${MAESTRO_APP_ID} +onFlowStart: + - runScript: ../api.js +--- +- evalScript: ${output.projectName = "Test project"} +- evalScript: ${output.questName = "Test quest"} +- runFlow: sign-in.yaml + +# Create project +- runFlow: + commands: + - tapOn: + id: app-drawer-menu-button + - tapOn: # go back to projects page + text: Projects + below: Connected + - tapOn: New Project + - tapOn: Project Name + - inputText: ${output.projectName} + # - inputText: Modern English + # - tapOn: '[Modern English]' - for some reason Maestro is not finding content in drawers + - assertVisible: + text: English + below: ${output.projectName} + - tapOn: Create + - assertVisible: + text: ${output.projectName}, English + +# Create quest +- runFlow: + commands: + - tapOn: ${output.projectName}, English + - tapOn: Create + - tapOn: Quest Name + - inputText: ${output.questName} + - pressKey: Enter + - tapOn: + text: Create + above: Cancel + - assertVisible: ${output.questName} + +# Cleanup +- evalScript: ${output.api.deleteProject(output.projectName)} diff --git a/maestro/register.yaml b/.maestro/flows/register.yaml similarity index 65% rename from maestro/register.yaml rename to .maestro/flows/register.yaml index 0362a8daa..b54dbedf6 100644 --- a/maestro/register.yaml +++ b/.maestro/flows/register.yaml @@ -1,19 +1,14 @@ appId: ${MAESTRO_APP_ID} onFlowStart: - - runScript: ./api.js -jsEngine: graaljs + - runScript: ../api.js --- - evalScript: ${output.username = faker.internet().username()} - evalScript: ${output.email = faker.internet().emailAddress()} -- launchApp: - clearState: true -- assertVisible: Terms & Privacy -- tapOn: - point: 8%,86% -- tapOn: Accept -- assertVisible: Every language. Every culture. -- tapOn: - id: onboarding-close-button +- runFlow: + when: + platform: iOS + file: _ios-toggle-autofill-passwords.yaml +- runFlow: _launch-and-onboard.yaml - tapOn: Create Account - tapOn: Username - inputText: ${output.username}-testing @@ -23,13 +18,13 @@ jsEngine: graaljs - inputText: password - tapOn: Confirm Password - inputText: password -- hideKeyboard +- pressKey: Enter - tapOn: - point: 10%,69% + id: checkbox # checkboxes are hard to find by maestro in LangQuest, using id - tapOn: Register - assertVisible: Projects - tapOn: - point: 89%,5% + id: app-drawer-menu-button - tapOn: Profile - assertVisible: Profile - evalScript: ${output.api.deleteUser(output.email)} diff --git a/maestro/reset-password.yaml b/.maestro/flows/reset-password.yaml similarity index 90% rename from maestro/reset-password.yaml rename to .maestro/flows/reset-password.yaml index 2eabb7cc9..9598dfcad 100644 --- a/maestro/reset-password.yaml +++ b/.maestro/flows/reset-password.yaml @@ -1,7 +1,6 @@ appId: ${MAESTRO_APP_ID} onFlowStart: - - runScript: ./api.js -jsEngine: graaljs + - runScript: ../api.js --- # Reset Password Test Flow # This test covers the complete password reset flow: @@ -22,15 +21,8 @@ jsEngine: graaljs - evalScript: ${output.resetLink = output.api.generatePasswordResetLink(MAESTRO_TEST_EMAIL)} # Step 2: Launch app and open the reset link -- launchApp: - clearState: true -- assertVisible: Terms & Privacy -- tapOn: - point: 8%,86% -- tapOn: Accept -- assertVisible: Every language. Every culture. -- tapOn: - id: onboarding-close-button +- runFlow: + file: _launch-and-onboard.yaml - openLink: link: ${output.resetLink} autoVerify: true diff --git a/.maestro/flows/sign-in.yaml b/.maestro/flows/sign-in.yaml new file mode 100644 index 000000000..f456e4808 --- /dev/null +++ b/.maestro/flows/sign-in.yaml @@ -0,0 +1,26 @@ +appId: ${MAESTRO_APP_ID} +--- +- runFlow: + when: + platform: iOS + file: _ios-toggle-autofill-passwords.yaml +- runFlow: _launch-and-onboard.yaml +- tapOn: + text: Sign In + index: -1 +- tapOn: Enter your email +- inputText: ${MAESTRO_TEST_EMAIL} +- tapOn: Enter your password +- inputText: ${MAESTRO_TEST_PASSWORD} +- tapOn: + point: 95%,30% +- tapOn: + text: Sign In + below: I forgot my password +- tapOn: + id: app-drawer-menu-button +- tapOn: Profile +- assertVisible: + text: Profile + above: + text: ${MAESTRO_TEST_EMAIL} diff --git a/.vscode/settings.json b/.vscode/settings.json index be018b0d9..0c3f9f28e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", + "files.eol": "\n", "tailwindCSS.classAttributes": [ "class", "className", diff --git a/app.config.ts b/app.config.ts index 55860183e..fe1100ff0 100644 --- a/app.config.ts +++ b/app.config.ts @@ -53,7 +53,7 @@ export default ({ config }: ConfigContext): ExpoConfig => owner: 'eten-genesis', name: getAppName(appVariant), slug: 'langquest', - version: '2.0.14', + version: '2.1.0', orientation: 'portrait', icon: iconLight, scheme: getScheme(appVariant), @@ -76,7 +76,6 @@ export default ({ config }: ConfigContext): ExpoConfig => } }, android: { - edgeToEdgeEnabled: true, adaptiveIcon: { foregroundImage: './assets/icons/adaptive-icon.png', monochromeImage: './assets/icons/adaptive-icon-mono.png', @@ -111,6 +110,8 @@ export default ({ config }: ConfigContext): ExpoConfig => 'expo-router', // TODO: migrate existing localization to expo-localization 'expo-localization', + 'expo-asset', + 'expo-audio', [ 'expo-splash-screen', { diff --git a/app/_layout.tsx b/app/_layout.tsx index 33672aab4..dcf6cde28 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -143,12 +143,12 @@ export default function RootLayout() { return ( - - - - - - + + + + + + @@ -164,12 +164,12 @@ export default function RootLayout() { - - - - - - + + + + + + ); } diff --git a/app/terms.tsx b/app/terms.tsx index d5d8681ff..52770b3cc 100644 --- a/app/terms.tsx +++ b/app/terms.tsx @@ -1,21 +1,14 @@ import { LanguageSelect } from '@/components/language-select'; -import { - Button, - ButtonPressableOpacity, - buttonTextVariants -} from '@/components/ui/button'; -import { Checkbox } from '@/components/ui/checkbox'; +import { Button, OpacityPressable } from '@/components/ui/button'; import { Icon } from '@/components/ui/icon'; -import { Label } from '@/components/ui/label'; import { Text } from '@/components/ui/text'; import { useLocalization } from '@/hooks/useLocalization'; import { useLocalStore } from '@/store/localStore'; -import { cn } from '@/utils/styleUtils'; import { useRouter } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; import { ArrowLeftIcon, XIcon } from 'lucide-react-native'; import React, { useCallback, useState } from 'react'; -import { Linking, Pressable, View } from 'react-native'; +import { Linking, View } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; function Terms() { @@ -23,7 +16,6 @@ function Terms() { const dateTermsAccepted = useLocalStore((state) => state.dateTermsAccepted); const acceptTerms = useLocalStore((state) => state.acceptTerms); const { t } = useLocalization(); - const [termsAccepted, setTermsAccepted] = useState(false); const handleAcceptTerms = useCallback(() => { console.log('Accepting terms...'); @@ -33,10 +25,6 @@ function Terms() { router.replace('/'); }, [acceptTerms, router]); - const handleToggleTerms = useCallback(() => { - setTermsAccepted(!termsAccepted); - }, [termsAccepted]); - const handleClosePress = useCallback(() => { if (router.canGoBack()) { router.back(); @@ -45,14 +33,6 @@ function Terms() { } }, [router]); - const handleViewTerms = useCallback(() => { - void Linking.openURL(`${process.env.EXPO_PUBLIC_SITE_URL}/terms`); - }, []); - - const handleViewPrivacy = useCallback(() => { - void Linking.openURL(`${process.env.EXPO_PUBLIC_SITE_URL}/privacy`); - }, []); - const canAcceptTerms = !dateTermsAccepted; const [languagesLoaded, setLanguagesLoaded] = useState(false); @@ -65,11 +45,11 @@ function Terms() { return ( - {router.canGoBack() && ( + {router.canGoBack() && dateTermsAccepted && ( @@ -89,56 +69,54 @@ function Terms() { - - {t('termsContributionInfo')} + + + {(() => { + // Get raw translation without replacing placeholders + const rawText = t('termsContributionInfo'); + // Split on {iAgree} placeholder (with optional spaces) + const placeholderRegex = /\{ *iAgree *\}/; + const match = rawText.match(placeholderRegex); + if (match) { + const parts = rawText.split(placeholderRegex); + const iAgreeText = t('iAgree'); + return ( + <> + {parts[0]} + {iAgreeText} + {parts[1]} + + ); + } + // Fallback if placeholder not found + return rawText; + })()} + {t('termsDataInfo')} {t('analyticsInfo')} - - - - {t('viewFullTerms')} - - - - - {t('viewFullPrivacy')} - - - + + Linking.openURL(`${process.env.EXPO_PUBLIC_SITE_URL}/terms`) + } + className="w-full justify-start" + > + {t('viewFullTerms')} + + + Linking.openURL(`${process.env.EXPO_PUBLIC_SITE_URL}/privacy`) + } + className="w-full justify-start" + > + {t('viewFullPrivacy')} + + {canAcceptTerms && ( - - - - - - - - )} diff --git a/components/AccountDeletedOverlay.tsx b/components/AccountDeletedOverlay.tsx index 5ab46b548..47a7eb9a0 100644 --- a/components/AccountDeletedOverlay.tsx +++ b/components/AccountDeletedOverlay.tsx @@ -72,6 +72,7 @@ export function AccountDeletedOverlay() { RNAlert.alert(t('success'), t('accountRestoreSuccess'), [ { text: t('ok'), + isPreferred: true, onPress: () => { // Navigate to projects page after restore goToProjects(); diff --git a/components/AppHeader.tsx b/components/AppHeader.tsx index baa427a73..544ecb938 100644 --- a/components/AppHeader.tsx +++ b/components/AppHeader.tsx @@ -259,6 +259,7 @@ export default function AppHeader({ onPress={drawerToggleCallback} className="relative size-8" hitSlop={10} + testID="app-drawer-menu-button" > diff --git a/components/AssetFilterModal.tsx b/components/AssetFilterModal.tsx index 7892de76b..6b867f04a 100644 --- a/components/AssetFilterModal.tsx +++ b/components/AssetFilterModal.tsx @@ -11,7 +11,17 @@ import { sharedStyles, spacing } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import { + ChevronUp, + ChevronDown, + CheckCircle2, + Filter, + ArrowUpDown, + ArrowUp, + ArrowDown, + Trash2 +} from 'lucide-react-native'; import React, { useEffect, useMemo, useState } from 'react'; import { ScrollView, @@ -88,10 +98,10 @@ const CategorySection: React.FC<{ {category} - @@ -106,11 +116,7 @@ const CategorySection: React.FC<{ {option.label} {selectedOptions.includes(option.id) ? ( - + ) : ( )} @@ -242,11 +248,13 @@ export const AssetFilterModal: React.FC = ({ onPress={() => setActiveTab('filter')} > - {getActiveFiltersCount() > 0 && ( @@ -263,10 +271,12 @@ export const AssetFilterModal: React.FC = ({ onPress={() => setActiveTab('sort')} > - {getActiveSortingCount() > 0 && ( @@ -333,14 +343,14 @@ export const AssetFilterModal: React.FC = ({ ) } > - {sortingOptions[index]?.field && ( @@ -348,10 +358,10 @@ export const AssetFilterModal: React.FC = ({ style={styles.removeButton} onPress={() => handleSortingChange(index, null)} > - )} diff --git a/components/AudioPlayer.tsx b/components/AudioPlayer.tsx index 7119b3e77..8ded44d1a 100644 --- a/components/AudioPlayer.tsx +++ b/components/AudioPlayer.tsx @@ -1,17 +1,13 @@ +import { Button } from '@/components/ui/button'; +import { Icon } from '@/components/ui/icon'; import { Slider } from '@/components/ui/slider'; import { useAudio } from '@/contexts/AudioContext'; import { useLocalization } from '@/hooks/useLocalization'; import { colors, fontSizes, spacing } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; -import { Audio } from 'expo-av'; +import { Play, Pause, FileText } from 'lucide-react-native'; +import { setAudioModeAsync } from 'expo-audio'; import React, { useEffect } from 'react'; -import { - ActivityIndicator, - StyleSheet, - Text, - TouchableOpacity, - View -} from 'react-native'; +import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'; import Carousel from './Carousel'; interface AudioFile { @@ -50,10 +46,10 @@ const AudioPlayer: React.FC = ({ useEffect(() => { const setupAudioMode = async () => { - await Audio.setAudioModeAsync({ - allowsRecordingIOS: false, - playsInSilentModeIOS: true, - staysActiveInBackground: true + await setAudioModeAsync({ + allowsRecording: false, + playsInSilentMode: true, + shouldPlayInBackground: true }); }; @@ -87,18 +83,20 @@ const AudioPlayer: React.FC = ({ return ( - handlePlayPause(item.uri, item.id)} > - - + {onTranscribe && ( - = ({ color={colors.background} /> ) : ( - )} - + )} {!mini && {item.title}} diff --git a/components/AudioRecorder.tsx b/components/AudioRecorder.tsx index dc3ec089b..8f55bd01c 100644 --- a/components/AudioRecorder.tsx +++ b/components/AudioRecorder.tsx @@ -5,12 +5,20 @@ import { Text } from '@/components/ui/text'; import { useAuth } from '@/contexts/AuthContext'; import { useLocalization } from '@/hooks/useLocalization'; import { deleteIfExists } from '@/utils/fileUtils'; -import type { AVPlaybackStatus } from 'expo-av'; -import { Audio } from 'expo-av'; -import type { RecordingOptions } from 'expo-av/build/Audio'; +import { + createAudioPlayer, + getRecordingPermissionsAsync, + RecordingPresets, + requestRecordingPermissionsAsync, + setAudioModeAsync, + useAudioRecorder, + useAudioRecorderState, + type AudioPlayer, + type RecordingOptions +} from 'expo-audio'; import type { LucideIcon } from 'lucide-react-native'; import { Check, Mic, Pause, Play } from 'lucide-react-native'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Platform, Pressable, View } from 'react-native'; // Maximum file size in bytes (50MB) @@ -29,10 +37,8 @@ interface AudioRecorderProps { } function calculateMaxDuration(options: RecordingOptions) { - const platform = Platform.OS === 'ios' ? 'ios' : 'android'; - const platformSpecificOptions = options[platform]; - // Using the exact bit rates from RecordingOptionsPresets - const bitRate = platformSpecificOptions.bitRate ?? 128000; // bits per second + // expo-audio has bitRate at the top level + const bitRate = options.bitRate ?? 128000; // bits per second // Convert bit rate to bytes per second const bytesPerSecond = bitRate / 8; @@ -49,124 +55,156 @@ const AudioRecorder: React.FC = ({ }) => { const { currentUser } = useAuth(); const { t } = useLocalization(); - const [recording, setRecording] = useState(null); - const [sound, setSound] = useState(null); const [recordingUri, setRecordingUri] = useState(null); const [recordingDuration, setRecordingDuration] = useState(0); const [playbackPosition, setPlaybackPosition] = useState(0); - const [isRecording, setIsRecording] = useState(false); + const [isRecordingActive, setIsRecordingActive] = useState(false); const [isRecordingPaused, setIsRecordingPaused] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [showWarning, setShowWarning] = useState(false); - const [permissionResponse, requestPermission] = Audio.usePermissions(); + const [permissionGranted, setPermissionGranted] = useState( + null + ); const [quality, setQuality] = useState('HIGH_QUALITY'); - // Calculate max duration and warning threshold based on quality - const maxDuration = calculateMaxDuration( - Audio.RecordingOptionsPresets[quality]! + // Refs for playback + const playerRef = useRef(null); + const playerListenerRef = useRef<{ remove: () => void } | null>(null); + const positionIntervalRef = useRef | null>( + null ); + + // Ref for stopRecording to avoid stale closure in status listener + const stopRecordingRef = useRef<(() => Promise) | undefined>(undefined); + + // Calculate max duration and warning threshold based on quality + const maxDuration = calculateMaxDuration(RecordingPresets[quality]!); const warningThreshold = maxDuration * 0.85; // Warning at 85% of max duration + // Check permissions on mount + useEffect(() => { + void getRecordingPermissionsAsync().then(({ granted }) => + setPermissionGranted(granted) + ); + }, []); + + // Create recorder (no status listener -- use useAudioRecorderState for duration/metering) + const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY); + + // Poll recording state for duration tracking + const recorderState = useAudioRecorderState(recorder, 100); + + // React to recorder state changes for duration tracking and auto-stop + useEffect(() => { + if (recorderState.isRecording) { + const duration = recorderState.durationMillis || 0; + setRecordingDuration(duration); + + // Check if we're approaching the limit + if (duration >= warningThreshold && !showWarning) { + setShowWarning(true); + } + + // Stop recording if we've reached the maximum duration + if (duration >= maxDuration) { + void stopRecordingRef.current?.(); + } + } + }, [recorderState, warningThreshold, showWarning, maxDuration]); + + const cleanupPlayer = useCallback(() => { + if (positionIntervalRef.current) { + clearInterval(positionIntervalRef.current); + positionIntervalRef.current = null; + } + if (playerListenerRef.current) { + playerListenerRef.current.remove(); + playerListenerRef.current = null; + } + if (playerRef.current) { + playerRef.current.pause(); + playerRef.current.release(); + playerRef.current = null; + } + }, []); + const stopRecording = useCallback(async () => { - if (!recording) return; + if (!recorder.isRecording) return; try { - await recording.stopAndUnloadAsync(); - await Audio.setAudioModeAsync({ - allowsRecordingIOS: false + await recorder.stop(); + await setAudioModeAsync({ + allowsRecording: false }); - const uri = recording.getURI(); + const uri = recorder.uri; if (recordingUri) { await deleteIfExists(recordingUri); console.log('Deleted previous recording attempt', recordingUri); } console.log('Recording stopped and stored at', uri); setRecordingUri(uri ?? null); - setRecording(null); - setIsRecording(false); + setIsRecordingActive(false); setIsRecordingPaused(false); if (uri) onRecordingComplete(uri); } catch (error) { console.error('Failed to stop recording:', error); } - }, [recording, recordingUri, onRecordingComplete]); + }, [recorder, recordingUri, onRecordingComplete]); + // Keep ref up to date for status listener + stopRecordingRef.current = stopRecording; + + // Cleanup on unmount useEffect(() => { return () => { - const cleanup = async () => { - if (sound?._loaded) { - await sound.stopAsync(); - await sound.unloadAsync(); - setSound(null); - setIsPlaying(false); - } - if (!recording?._isDoneRecording) await stopRecording(); - }; - void cleanup(); + cleanupPlayer(); + // Recorder cleanup is handled by useAudioRecorder hook }; - }, [recording, sound, stopRecording]); + }, [cleanupPlayer]); const startRecording = async () => { try { if (!currentUser) return; - if (permissionResponse?.status !== Audio.PermissionStatus.GRANTED) { + if (!permissionGranted) { console.log('Requesting permission..'); - await requestPermission(); + const { granted } = await requestRecordingPermissionsAsync(); + setPermissionGranted(granted); + if (!granted) return; } resetRecording?.(); - await Audio.setAudioModeAsync({ - allowsRecordingIOS: true, - playsInSilentModeIOS: true + await setAudioModeAsync({ + allowsRecording: true, + playsInSilentMode: true }); - // resume recording if it paused - if (recording) { - await recording.startAsync(); - setIsRecording(true); + // Resume recording if it was paused + if (isRecordingPaused) { + recorder.record(); + setIsRecordingActive(true); setIsRecordingPaused(false); return; } console.log('recording'); - const activeRecording = ( - await Audio.Recording.createAsync( - Audio.RecordingOptionsPresets[quality] - ) - ).recording; + // Prepare and start a new recording with the selected quality + await recorder.prepareToRecordAsync(RecordingPresets[quality]); + recorder.record(); - setRecording(activeRecording); - setIsRecording(true); + setIsRecordingActive(true); setIsRecordingPaused(false); setShowWarning(false); - // Start monitoring recording status - activeRecording.setOnRecordingStatusUpdate((status) => { - if (status.isRecording) { - const duration = status.durationMillis || 0; - setRecordingDuration(duration); - - // Check if we're approaching the limit - if (duration >= warningThreshold && !showWarning) { - setShowWarning(true); - } - - // Stop recording if we've reached the maximum duration - if (duration >= maxDuration) { - void stopRecording(); - } - } - }); } catch (error) { console.error('Failed to start recording:', error); } }; const pauseRecording = async () => { - if (!recording) return; - await recording.pauseAsync(); - setIsRecording(false); + if (!recorder.isRecording) return; + recorder.pause(); + setIsRecordingActive(false); setIsRecordingPaused(true); }; @@ -174,18 +212,41 @@ const AudioRecorder: React.FC = ({ if (!recordingUri) return; try { - if (sound) { - // If sound exists, just replay it from the beginning - if (playbackPosition === 0) await sound.setPositionAsync(0); - await sound.playAsync(); + if (playerRef.current) { + // If player exists, just replay it + if (playbackPosition === 0) playerRef.current.seekTo(0); + playerRef.current.play(); } else { - // Only create a new sound if one doesn't exist yet - const { sound: newSound } = await Audio.Sound.createAsync( - { uri: recordingUri }, - { shouldPlay: true }, - onPlaybackStatusUpdate + // Create a new player + await setAudioModeAsync({ + allowsRecording: false, + playsInSilentMode: true + }); + + const player = createAudioPlayer(recordingUri); + playerRef.current = player; + player.play(); + + // Listen for playback end + playerListenerRef.current = player.addListener( + 'playbackStatusUpdate', + (status) => { + if (!status.didJustFinish) return; + setIsPlaying(false); + setPlaybackPosition(0); + if (positionIntervalRef.current) { + clearInterval(positionIntervalRef.current); + positionIntervalRef.current = null; + } + } ); - setSound(newSound); + + // Track position + positionIntervalRef.current = setInterval(() => { + if (playerRef.current?.isLoaded) { + setPlaybackPosition(playerRef.current.currentTime * 1000); + } + }, 100); } setIsPlaying(true); @@ -195,26 +256,16 @@ const AudioRecorder: React.FC = ({ }; const pausePlayback = async () => { - if (!sound) return; + if (!playerRef.current) return; try { - await sound.pauseAsync(); + playerRef.current.pause(); setIsPlaying(false); } catch (error) { console.error('Failed to pause playback:', error); } }; - const onPlaybackStatusUpdate = (status: AVPlaybackStatus) => { - if (status.isLoaded) { - setPlaybackPosition(status.positionMillis); - if (status.didJustFinish) { - setIsPlaying(false); - setPlaybackPosition(0); - } - } - }; - const formatTime = (milliseconds: number): string => { const totalSeconds = Math.floor(milliseconds / 1000); const hours = Math.floor(totalSeconds / 3600); @@ -237,7 +288,7 @@ const AudioRecorder: React.FC = ({ }; const getButtonConfig = (): [ButtonConfig, ButtonConfig] => { - if (isRecording || isRecordingPaused) { + if (isRecordingActive || isRecordingPaused) { return [ { icon: isRecordingPaused ? Mic : Pause, @@ -271,7 +322,7 @@ const AudioRecorder: React.FC = ({ { icon: Check, onPress: undefined, - disabled: !recording + disabled: !isRecordingActive } ]; }; diff --git a/components/AudioSegmentItem.tsx b/components/AudioSegmentItem.tsx index 17c2df809..ee0f79013 100644 --- a/components/AudioSegmentItem.tsx +++ b/components/AudioSegmentItem.tsx @@ -1,5 +1,12 @@ import { colors, fontSizes, spacing } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import { + ChevronUp, + ChevronDown, + Play, + Pause, + Trash2 +} from 'lucide-react-native'; import React from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import WaveformVisualizer from './WaveformVisualizer'; @@ -48,10 +55,10 @@ const AudioSegmentItem: React.FC = ({ onPress={() => canMoveUp && onMoveUp(segment.id)} disabled={!canMoveUp} > - @@ -60,10 +67,12 @@ const AudioSegmentItem: React.FC = ({ onPress={() => canMoveDown && onMoveDown(segment.id)} disabled={!canMoveDown} > - @@ -98,10 +107,10 @@ const AudioSegmentItem: React.FC = ({ style={styles.playButton} onPress={() => onPlay(segment.uri)} > - )} @@ -110,7 +119,7 @@ const AudioSegmentItem: React.FC = ({ style={styles.deleteButton} onPress={() => onDelete(segment.id)} > - + diff --git a/components/Carousel.native.tsx b/components/Carousel.native.tsx index a0e00f821..682061255 100644 --- a/components/Carousel.native.tsx +++ b/components/Carousel.native.tsx @@ -1,5 +1,6 @@ import { borderRadius, colors, spacing } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import { ChevronLeft, ChevronRight } from 'lucide-react-native'; import type { ReactNode } from 'react'; import React, { useRef, useState } from 'react'; import { StyleSheet, TouchableOpacity, View } from 'react-native'; @@ -49,7 +50,7 @@ function Carousel({ items, renderItem, onPageChange }: CarouselProps) { onPress={() => pagerRef.current?.setPage(currentPage - 1)} disabled={currentPage === 0} > - + {items.map((_, index) => ( @@ -71,7 +72,7 @@ function Carousel({ items, renderItem, onPageChange }: CarouselProps) { onPress={() => pagerRef.current?.setPage(currentPage + 1)} disabled={currentPage === items.length - 1} > - + diff --git a/components/Carousel.tsx b/components/Carousel.tsx index b18d8503c..89f0815ad 100644 --- a/components/Carousel.tsx +++ b/components/Carousel.tsx @@ -1,5 +1,6 @@ import { borderRadius, colors, spacing } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import { ChevronLeft, ChevronRight } from 'lucide-react-native'; import type { ReactNode } from 'react'; import React, { useRef, useState } from 'react'; import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native'; @@ -68,7 +69,7 @@ function Carousel({ items, renderItem, onPageChange }: CarouselProps) { onPress={() => scrollToPage(currentPage - 1)} disabled={currentPage === 0} > - + {items.map((_, index) => ( @@ -90,7 +91,7 @@ function Carousel({ items, renderItem, onPageChange }: CarouselProps) { onPress={() => scrollToPage(currentPage + 1)} disabled={currentPage === items.length - 1} > - + diff --git a/components/Carousel.web.tsx b/components/Carousel.web.tsx index b18d8503c..89f0815ad 100644 --- a/components/Carousel.web.tsx +++ b/components/Carousel.web.tsx @@ -1,5 +1,6 @@ import { borderRadius, colors, spacing } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import { ChevronLeft, ChevronRight } from 'lucide-react-native'; import type { ReactNode } from 'react'; import React, { useRef, useState } from 'react'; import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native'; @@ -68,7 +69,7 @@ function Carousel({ items, renderItem, onPageChange }: CarouselProps) { onPress={() => scrollToPage(currentPage - 1)} disabled={currentPage === 0} > - + {items.map((_, index) => ( @@ -90,7 +91,7 @@ function Carousel({ items, renderItem, onPageChange }: CarouselProps) { onPress={() => scrollToPage(currentPage + 1)} disabled={currentPage === items.length - 1} > - + diff --git a/components/CustomDropdown.tsx b/components/CustomDropdown.tsx index d5fa91c6a..a1c6a3c96 100644 --- a/components/CustomDropdown.tsx +++ b/components/CustomDropdown.tsx @@ -20,7 +20,7 @@ interface CustomDropdownProps { search?: boolean; searchPlaceholder?: string; containerStyle?: object; - renderLeftIcon?: (visible?: boolean) => React.ReactElement | null | undefined; + renderLeftIcon?: (visible?: boolean) => React.ReactElement | null; } export const CustomDropdown: React.FC = ({ diff --git a/components/DownloadIndicator.tsx b/components/DownloadIndicator.tsx index 427720efd..4d9da9dbd 100644 --- a/components/DownloadIndicator.tsx +++ b/components/DownloadIndicator.tsx @@ -1,10 +1,11 @@ +import { Button } from '@/components/ui/button'; import { useAuth } from '@/contexts/AuthContext'; import { useNetworkStatus } from '@/hooks/useNetworkStatus'; import { storage } from '@/utils/storage'; import { cn, useThemeColor } from '@/utils/styleUtils'; import { CircleArrowDownIcon, CircleCheckIcon } from 'lucide-react-native'; import React, { useState } from 'react'; -import { ActivityIndicator, TouchableOpacity } from 'react-native'; +import { ActivityIndicator } from 'react-native'; import { DownloadConfirmationModal } from './DownloadConfirmationModal'; import { OfflineUndownloadWarning } from './OfflineUndownloadWarning'; import { Icon } from './ui/icon'; @@ -116,7 +117,9 @@ export const DownloadIndicator: React.FC = ({ return ( <> - = ({ ) : ( )} - + {/* Download confirmation modal */} {downloadType && stats && ( diff --git a/components/EnergyVADRecorder.tsx b/components/EnergyVADRecorder.tsx index f7165df9c..0b7f56c86 100644 --- a/components/EnergyVADRecorder.tsx +++ b/components/EnergyVADRecorder.tsx @@ -1,15 +1,16 @@ -import { Ionicons } from '@expo/vector-icons'; -import { Audio } from 'expo-av'; +import { Icon } from '@/components/ui/icon'; +import { StopCircle, PlayCircle } from 'lucide-react-native'; +import { + RecordingPresets, + setAudioModeAsync, + useAudioRecorder +} from 'expo-audio'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import RNAlert from '@blazejkustra/react-native-alert'; import { useMicrophoneEnergy } from '../hooks/useMicrophoneEnergy'; import { colors, fontSizes, spacing } from '../styles/theme'; -// Global recording state to prevent multiple concurrent recordings -let globalRecordingInstance: Audio.Recording | null = null; -let globalRecordingInProgress = false; - interface EnergyVADRecorderProps { onRecordingComplete: (uri: string, segmentIndex: number) => void; energyThreshold?: number; @@ -25,7 +26,10 @@ const EnergyVADRecorder: React.FC = ({ const [currentEnergy, setCurrentEnergy] = useState(0); const [threshold, setThreshold] = useState(energyThreshold); const [segmentCount, setSegmentCount] = useState(0); - const recordingRef = useRef(null); + // Guard against concurrent recording attempts + const isRecordingInProgressRef = useRef(false); + + const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY); const { isActive, @@ -36,95 +40,53 @@ const EnergyVADRecorder: React.FC = ({ error } = useMicrophoneEnergy(); - // Initialize: Clean up any orphaned recording state on mount - useEffect(() => { - if (globalRecordingInstance && !isRecording) { - console.log('Cleaning up orphaned recording on mount'); - globalRecordingInstance.stopAndUnloadAsync().catch((_error) => { - console.log('Orphaned recording already cleaned up'); - }); - globalRecordingInstance = null; - globalRecordingInProgress = false; - } - }, [isRecording]); // Run only on mount - const startRecording = useCallback(async () => { try { - // Prevent starting if already recording globally or locally - if ( - globalRecordingInProgress || - isRecording || - recordingRef.current || - globalRecordingInstance - ) { - console.log('Recording already in progress (global or local)'); + // Prevent starting if already recording + if (isRecordingInProgressRef.current || isRecording) { + console.log('Recording already in progress'); return; } - console.log('Starting expo-av recording (energy-triggered)...'); - globalRecordingInProgress = true; + console.log('Starting recording (energy-triggered)...'); + isRecordingInProgressRef.current = true; // Small delay to ensure any previous recording is fully cleaned up await new Promise((resolve) => setTimeout(resolve, 50)); - await Audio.setAudioModeAsync({ - allowsRecordingIOS: true, - playsInSilentModeIOS: true + await setAudioModeAsync({ + allowsRecording: true, + playsInSilentMode: true }); - const { recording } = await Audio.Recording.createAsync( - Audio.RecordingOptionsPresets.HIGH_QUALITY - ); + await recorder.prepareToRecordAsync(RecordingPresets.HIGH_QUALITY); + recorder.record(); - globalRecordingInstance = recording; - recordingRef.current = recording; setIsRecording(true); - globalRecordingInProgress = false; + isRecordingInProgressRef.current = false; } catch (error) { console.error('Failed to start recording:', error); - // Clean up all state on error - globalRecordingInstance = null; - recordingRef.current = null; setIsRecording(false); - globalRecordingInProgress = false; + isRecordingInProgressRef.current = false; } - }, [isRecording]); + }, [isRecording, recorder]); const stopRecording = useCallback(async () => { try { - console.log('Stopping expo-av recording...'); - - const recordingToStop = recordingRef.current || globalRecordingInstance; + console.log('Stopping recording...'); - if (!recordingToStop || !isRecording) { + if (!isRecording) { console.log('No active recording to stop'); - // Clean up all state - globalRecordingInstance = null; - recordingRef.current = null; setIsRecording(false); - globalRecordingInProgress = false; + isRecordingInProgressRef.current = false; return; } - // Check if recording is actually in progress - const status = await recordingToStop.getStatusAsync(); - if (!status.isRecording) { - console.log('Recording is not active, cleaning up reference'); - globalRecordingInstance = null; - recordingRef.current = null; - setIsRecording(false); - globalRecordingInProgress = false; - return; - } - - await recordingToStop.stopAndUnloadAsync(); - const uri = recordingToStop.getURI(); + await recorder.stop(); + const uri = recorder.uri; - // Clean up all references - globalRecordingInstance = null; - recordingRef.current = null; setIsRecording(false); - globalRecordingInProgress = false; + isRecordingInProgressRef.current = false; if (uri) { const currentSegment = segmentCount; @@ -137,13 +99,10 @@ const EnergyVADRecorder: React.FC = ({ } } catch (error) { console.error('Failed to stop recording:', error); - // Clean up all state even if stopping failed - globalRecordingInstance = null; - recordingRef.current = null; setIsRecording(false); - globalRecordingInProgress = false; + isRecordingInProgressRef.current = false; } - }, [onRecordingComplete, isRecording, segmentCount]); + }, [onRecordingComplete, isRecording, segmentCount, recorder]); // Handle energy levels and control recording useEffect(() => { @@ -178,9 +137,7 @@ const EnergyVADRecorder: React.FC = ({ } await stopEnergyDetection(); - // Clean up global state when stopping energy detection - globalRecordingInstance = null; - globalRecordingInProgress = false; + isRecordingInProgressRef.current = false; setSegmentCount(0); // Reset segment count when stopping detection } else { await startEnergyDetection(); @@ -188,11 +145,8 @@ const EnergyVADRecorder: React.FC = ({ } catch (error) { RNAlert.alert('Error', 'Failed to toggle energy detection'); console.error('Energy detection toggle error:', error); - // Clean up state on error - globalRecordingInstance = null; - recordingRef.current = null; setIsRecording(false); - globalRecordingInProgress = false; + isRecordingInProgressRef.current = false; } }, [ isActive, @@ -202,23 +156,10 @@ const EnergyVADRecorder: React.FC = ({ startEnergyDetection ]); - // Cleanup on unmount + // Cleanup on unmount (recorder lifecycle handled by useAudioRecorder hook) useEffect(() => { return () => { - // Clean up recording if it exists (check both local and global) - const recordingToCleanup = - recordingRef.current || globalRecordingInstance; - if (recordingToCleanup) { - recordingToCleanup.stopAndUnloadAsync().catch((_error) => { - console.log('Cleanup: Recording already stopped or released'); - }); - } - - // Reset all recording state - globalRecordingInstance = null; - recordingRef.current = null; - setIsRecording(false); - globalRecordingInProgress = false; + isRecordingInProgressRef.current = false; }; }, []); @@ -231,10 +172,10 @@ const EnergyVADRecorder: React.FC = ({ style={[styles.vadButton, isActive && styles.vadButtonActive]} onPress={handleToggleEnergyDetection} > - {isActive ? 'Stop Energy Detection' : 'Start Energy Detection'} diff --git a/components/FloatingMenu.tsx b/components/FloatingMenu.tsx index 53a161057..151311618 100644 --- a/components/FloatingMenu.tsx +++ b/components/FloatingMenu.tsx @@ -1,10 +1,12 @@ import { colors } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import type { LucideIcon } from 'lucide-react-native'; +import { Menu, X } from 'lucide-react-native'; import { useEffect, useRef, useState } from 'react'; import { Animated, StyleSheet, TouchableOpacity, View } from 'react-native'; export interface FloatingMenuItem { - icon: keyof typeof Ionicons.glyphMap; + icon: LucideIcon; label: string; action: () => void; } @@ -164,11 +166,7 @@ export const FloatingMenu = ({ activeOpacity={0.8} > - + @@ -176,6 +174,7 @@ export const FloatingMenu = ({ }; const ItemButton = ({ icon, action }: FloatingMenuItem) => { + const IconComponent = icon; return ( { activeOpacity={0.7} > - + ); }; export function createMenuItem( - iconString: string, + icon: LucideIcon, label: string, onClick: () => void ) { return { - icon: iconString as keyof typeof Ionicons.glyphMap, + icon, label, action: () => { onClick(); diff --git a/components/LanguageSelect.tsx b/components/LanguageSelect.tsx index 2c1de0a37..799548f82 100644 --- a/components/LanguageSelect.tsx +++ b/components/LanguageSelect.tsx @@ -6,7 +6,8 @@ import { import { useLocalization } from '@/hooks/useLocalization'; import { useLocalStore } from '@/store/localStore'; import { colors, spacing } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import { Languages } from 'lucide-react-native'; import { memo, default as React, @@ -88,10 +89,10 @@ const LanguageSelect: React.FC = memo( const renderLeftIcon = useCallback( () => ( - ), diff --git a/components/MiniAudioPlayer.tsx b/components/MiniAudioPlayer.tsx index 59f18beab..1cf98af53 100644 --- a/components/MiniAudioPlayer.tsx +++ b/components/MiniAudioPlayer.tsx @@ -1,17 +1,12 @@ +import { Button } from '@/components/ui/button'; import { Icon } from '@/components/ui/icon'; import { Text } from '@/components/ui/text'; import { useAudio } from '@/contexts/AudioContext'; -import { colors, spacing } from '@/styles/theme'; -import { getThemeColor } from '@/utils/styleUtils'; -import { Ionicons } from '@expo/vector-icons'; -import { SparklesIcon } from 'lucide-react-native'; +import { spacing } from '@/styles/theme'; +import { getThemeColor, useThemeColor } from '@/utils/styleUtils'; +import { Play, Pause, SparklesIcon } from 'lucide-react-native'; import React from 'react'; -import { - ActivityIndicator, - StyleSheet, - TouchableOpacity, - View -} from 'react-native'; +import { ActivityIndicator, StyleSheet, View } from 'react-native'; interface MiniAudioPlayerProps { id: string; @@ -35,6 +30,7 @@ export default function MiniAudioPlayer({ isPlaying, currentAudioId } = useAudio(); + const primaryColor = useThemeColor('primary'); const handlePlayPause = async () => { if (isPlaying && currentAudioId === id) { @@ -60,17 +56,22 @@ export default function MiniAudioPlayer({ return ( - - + - + {onTranscribe && ( - {isTranscribing ? ( @@ -80,11 +81,15 @@ export default function MiniAudioPlayer({ /> ) : ( - - Aa + + Aa )} - + )} ); @@ -102,14 +107,12 @@ const styles = StyleSheet.create({ width: 44, height: 44, borderRadius: 22, - backgroundColor: colors.primary, alignItems: 'center', justifyContent: 'center' }, transcribePill: { height: 32, borderRadius: 16, - backgroundColor: colors.primary, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 12 diff --git a/components/NewReportModal.tsx b/components/NewReportModal.tsx index 4c2e04647..455156213 100644 --- a/components/NewReportModal.tsx +++ b/components/NewReportModal.tsx @@ -92,6 +92,7 @@ export const ReportModal: React.FC = ({ { text: t('cancel'), style: 'cancel' }, { text: t('signIn') || 'Sign In', + isPreferred: true, onPress: () => { onClose(); setAuthView('sign-in'); @@ -278,9 +279,7 @@ export const ReportModal: React.FC = ({ - - - + ); }; diff --git a/components/PasswordInput.tsx b/components/PasswordInput.tsx index 3ed456566..d9d34f8a9 100644 --- a/components/PasswordInput.tsx +++ b/components/PasswordInput.tsx @@ -1,7 +1,9 @@ -import { Ionicons } from '@expo/vector-icons'; +import { Button } from '@/components/ui/button'; +import { Icon } from '@/components/ui/icon'; +import { Eye, EyeOff } from 'lucide-react-native'; import React, { useState } from 'react'; import type { StyleProp, ViewStyle } from 'react-native'; -import { StyleSheet, TextInput, TouchableOpacity, View } from 'react-native'; +import { StyleSheet, TextInput, View } from 'react-native'; interface PasswordInputProps { value: string; @@ -30,20 +32,20 @@ export const PasswordInput = ({ style={[styles.input, { color: style?.color }]} placeholderTextColor={placeholderTextColor} /> - { setShowPassword(!showPassword); }} - activeOpacity={1} style={styles.icon} > - - + ); }; diff --git a/components/PrivateAccessGate.tsx b/components/PrivateAccessGate.tsx index e87f6ca7d..4e54cd83e 100644 --- a/components/PrivateAccessGate.tsx +++ b/components/PrivateAccessGate.tsx @@ -325,6 +325,7 @@ export const PrivateAccessGate: React.FC = ({ { text: t('confirm'), style: 'destructive', + isPreferred: true, onPress: () => { void (async () => { setIsSubmitting(true); diff --git a/components/ProjectDetails.tsx b/components/ProjectDetails.tsx index 60a4a1731..e1b842881 100644 --- a/components/ProjectDetails.tsx +++ b/components/ProjectDetails.tsx @@ -3,7 +3,8 @@ import { languoid, project_language_link } from '@/db/drizzleSchema'; import { system } from '@/db/powersync/system'; import { borderRadius, colors, fontSizes, spacing } from '@/styles/theme'; import { useHybridData } from '@/views/new/useHybridData'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import { Languages, Info } from 'lucide-react-native'; import { toCompilableQuery } from '@powersync/drizzle-driver'; import { and, eq } from 'drizzle-orm'; import { default as React } from 'react'; @@ -110,7 +111,7 @@ export const ProjectDetails: React.FC = ({ {project.name} - + {sourceLanguoids.length ? sourceLanguoids @@ -124,11 +125,7 @@ export const ProjectDetails: React.FC = ({ {project.description && ( - + {project.description} )} diff --git a/components/ProjectMembershipModal.tsx b/components/ProjectMembershipModal.tsx index 6544699b2..7b8c08cb1 100644 --- a/components/ProjectMembershipModal.tsx +++ b/components/ProjectMembershipModal.tsx @@ -356,6 +356,7 @@ export const ProjectMembershipModal: React.FC = ({ { text: t('remove'), style: 'destructive', + isPreferred: true, onPress: () => { void (async () => { try { @@ -392,6 +393,7 @@ export const ProjectMembershipModal: React.FC = ({ { text: t('cancel'), style: 'cancel' }, { text: t('confirm'), + isPreferred: true, onPress: () => { void (async () => { try { @@ -434,6 +436,7 @@ export const ProjectMembershipModal: React.FC = ({ { text: t('confirm'), style: 'destructive', + isPreferred: true, onPress: () => { void (async () => { try { @@ -545,6 +548,7 @@ export const ProjectMembershipModal: React.FC = ({ { text: t('cancel'), style: 'cancel' }, { text: t('confirm'), + isPreferred: true, onPress: () => { void (async () => { setIsSubmitting(true); @@ -617,6 +621,7 @@ export const ProjectMembershipModal: React.FC = ({ { text: t('confirm'), style: 'destructive', + isPreferred: true, onPress: () => { void (async () => { setIsSubmitting(true); diff --git a/components/QuestFilterModal.tsx b/components/QuestFilterModal.tsx index 1569c3b6d..4e4927ae3 100644 --- a/components/QuestFilterModal.tsx +++ b/components/QuestFilterModal.tsx @@ -11,7 +11,17 @@ import { sharedStyles, spacing } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import { + ChevronUp, + ChevronDown, + CheckCircle2, + Filter, + ArrowUpDown, + ArrowUp, + ArrowDown, + Trash2 +} from 'lucide-react-native'; import React, { useEffect, useMemo, useState } from 'react'; import { ScrollView, @@ -88,10 +98,10 @@ const CategorySection: React.FC<{ {category} - @@ -106,11 +116,7 @@ const CategorySection: React.FC<{ {option.label} {selectedOptions.includes(option.id) ? ( - + ) : ( )} @@ -242,11 +248,13 @@ export const QuestFilterModal: React.FC = ({ onPress={() => setActiveTab('filter')} > - {getActiveFiltersCount() > 0 && ( @@ -263,10 +271,12 @@ export const QuestFilterModal: React.FC = ({ onPress={() => setActiveTab('sort')} > - {getActiveSortingCount() > 0 && ( @@ -333,14 +343,14 @@ export const QuestFilterModal: React.FC = ({ ) } > - {sortingOptions[index]?.field && ( @@ -348,10 +358,10 @@ export const QuestFilterModal: React.FC = ({ style={styles.removeButton} onPress={() => handleSortingChange(index, null)} > - )} diff --git a/components/RadioSelect.tsx b/components/RadioSelect.tsx index 1c406b0f8..7066db4ac 100644 --- a/components/RadioSelect.tsx +++ b/components/RadioSelect.tsx @@ -1,6 +1,7 @@ +import { Button } from '@/components/ui/button'; import { colors } from '@/styles/theme'; import React from 'react'; -import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; interface Option { label: string; @@ -29,7 +30,8 @@ export default function RadioSelect({ {options.map((option) => { const selected = value === option.value; return ( - onChange(option.value)} @@ -44,7 +46,7 @@ export default function RadioSelect({ {option.label} - + ); })} diff --git a/components/TranscriptionEditModal.tsx b/components/TranscriptionEditModal.tsx index 19648ba28..d444262e6 100644 --- a/components/TranscriptionEditModal.tsx +++ b/components/TranscriptionEditModal.tsx @@ -13,11 +13,11 @@ import { KeyboardAvoidingView, Modal, Platform, - SafeAreaView, TextInput, TouchableOpacity, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; interface TranscriptionEditModalProps { visible: boolean; @@ -132,7 +132,12 @@ export default function TranscriptionEditModal({ 'You have unsaved changes. Are you sure you want to close?', [ { text: t('cancel') || 'Cancel', style: 'cancel' }, - { text: 'Discard', style: 'destructive', onPress: onClose } + { + text: 'Discard', + style: 'destructive', + isPreferred: true, + onPress: onClose + } ] ); } else { diff --git a/components/TranslationCard.tsx b/components/TranslationCard.tsx index 913732631..238722452 100644 --- a/components/TranslationCard.tsx +++ b/components/TranslationCard.tsx @@ -9,7 +9,8 @@ import type { WithSource } from '@/utils/dbUtils'; import { SHOW_DEV_ELEMENTS } from '@/utils/featureFlags'; import { cn } from '@/utils/styleUtils'; import { ThumbsDownIcon, ThumbsUpIcon } from 'lucide-react-native'; -import { TouchableOpacity, View } from 'react-native'; +import { View } from 'react-native'; +import { Button } from './ui/button'; import AudioPlayer from './AudioPlayer'; interface TranslationCardProps { @@ -46,7 +47,7 @@ export const TranslationCard = ({ }; return ( - + ); }; diff --git a/components/TranslationSettingsModal.tsx b/components/TranslationSettingsModal.tsx index 6fc4a9180..dd7709ae8 100644 --- a/components/TranslationSettingsModal.tsx +++ b/components/TranslationSettingsModal.tsx @@ -12,7 +12,8 @@ import { sharedStyles, spacing } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import { X } from 'lucide-react-native'; import React, { useState } from 'react'; import { Modal, @@ -122,7 +123,7 @@ export const TranslationSettingsModal: React.FC< {'Translation Settings'} - + diff --git a/components/UpdateBanner.tsx b/components/UpdateBanner.tsx index 41ff044eb..eed35a616 100644 --- a/components/UpdateBanner.tsx +++ b/components/UpdateBanner.tsx @@ -5,7 +5,7 @@ import { useExpoUpdates } from '@/hooks/useExpoUpdates'; import { useLocalization } from '@/hooks/useLocalization'; import { CloudDownload, XIcon } from 'lucide-react-native'; import React from 'react'; -import { ActivityIndicator, TouchableOpacity, View } from 'react-native'; +import { ActivityIndicator, View } from 'react-native'; // DEV ONLY: Import mock for testing // To test OTA updates in development, uncomment the next 2 lines: @@ -71,13 +71,14 @@ export function UpdateBanner() { )} - - + ); diff --git a/components/VoteCommentModal.tsx b/components/VoteCommentModal.tsx index 841d52c70..940a55f6e 100644 --- a/components/VoteCommentModal.tsx +++ b/components/VoteCommentModal.tsx @@ -1,7 +1,8 @@ import { useAuth } from '@/contexts/AuthContext'; import { useLocalization } from '@/hooks/useLocalization'; import { borderRadius, colors, fontSizes, spacing } from '@/styles/theme'; -import { Ionicons } from '@expo/vector-icons'; +import { Icon } from '@/components/ui/icon'; +import { ThumbsUp, ThumbsDown } from 'lucide-react-native'; import React, { useState } from 'react'; import { Modal, @@ -68,10 +69,10 @@ export const VoteCommentModal: React.FC = ({ e.stopPropagation()}> - = ({ const haptic = useHaptic('medium'); const { currentUser: _currentUser } = useAuth(); const { t } = useLocalization(); - const [recording, setRecording] = useState(null); + const [hasActiveRecording, setHasActiveRecording] = useState(false); const [recordingDuration, setRecordingDuration] = useState(0); // Permission check removed - handled by parent RecordingControls via canRecord prop const isActivatingRef = useRef(false); @@ -89,6 +95,58 @@ const WalkieTalkieRecorder: React.FC = ({ // Cancellation flag - set when user releases during async setup const shouldCancelRecordingRef = useRef(false); + // Energy range tracking for logging + const energyRangeRef = useRef({ min: Infinity, max: -Infinity }); + + // Audio recorder from expo-audio (manages lifecycle automatically) + const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY); + + // Poll recording state for duration and metering data + const recorderState = useAudioRecorderState(recorder, 50); + + // Previous duration ref to avoid duplicate processing + const lastProcessedDurationRef = useRef(0); + + // React to recorder state changes for duration tracking and metering + useEffect(() => { + if (!recorderState.isRecording) return; + + const duration = recorderState.durationMillis || 0; + + // Skip if we already processed this duration (avoid duplicate work) + if (duration === lastProcessedDurationRef.current) return; + lastProcessedDurationRef.current = duration; + + setRecordingDuration(duration); + // Notify parent of duration updates for progress bar + onRecordingDurationUpdate?.(duration); + + let amplitude: number; + if (typeof recorderState.metering === 'number') { + const db = recorderState.metering; + const normalizedDb = Math.max(-60, Math.min(0, db)); + amplitude = Math.pow(10, normalizedDb / 20); + appendLiveSample(amplitude); + } else { + const t = duration / 1000; + const base = 0.3 + Math.sin(t * 24) * 0.15; + const noise = (Math.random() - 0.5) * 0.1; + amplitude = Math.max(0.02, Math.min(0.8, base + noise)); + appendLiveSample(amplitude); + } + + // Track energy range + energyRangeRef.current.min = Math.min( + energyRangeRef.current.min, + amplitude + ); + energyRangeRef.current.max = Math.max( + energyRangeRef.current.max, + amplitude + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [recorderState]); + // ============================================================================ // BUSY LOCK - Prevents race conditions during state transitions // ============================================================================ @@ -185,23 +243,18 @@ const WalkieTalkieRecorder: React.FC = ({ } }; - // Cleanup recording and timers on unmount + // Cleanup timers on unmount (recorder cleanup handled by useAudioRecorder hook) useEffect(() => { return () => { - const cleanup = async () => { - if (activationTimer.current) { - clearTimeout(activationTimer.current); - } - if (releaseDelayTimer.current) { - clearTimeout(releaseDelayTimer.current); - } - if (recording && !recording._isDoneRecording) { - await recording.stopAndUnloadAsync(); - } - }; - void cleanup(); + if (activationTimer.current) { + clearTimeout(activationTimer.current); + } + if (releaseDelayTimer.current) { + clearTimeout(releaseDelayTimer.current); + } + // Recorder cleanup is handled automatically by useAudioRecorder hook }; - }, [recording]); + }, []); // Pulse animation when recording useEffect(() => { @@ -249,18 +302,6 @@ const WalkieTalkieRecorder: React.FC = ({ requestAnimationFrame(() => resolve(undefined)) ); - const startTime = performance.now(); - - // Clean up any existing recording first - if (recording) { - try { - await recording.stopAndUnloadAsync(); - } catch { - // Ignore cleanup errors - } - setRecording(null); - } - setRecordedSamples([]); // Permission check removed - parent RecordingControls ensures canRecord=true @@ -269,18 +310,19 @@ const WalkieTalkieRecorder: React.FC = ({ // Reset cancellation flag at start shouldCancelRecordingRef.current = false; - // ✅ Notify parent IMMEDIATELY - we're in "recording mode" now + // Notify parent IMMEDIATELY - we're in "recording mode" now // This ensures isRecording is true synchronously, preventing race conditions // where user releases before async setup completes onRecordingStart(); // Heavy operations - but user already sees feedback - await Audio.setAudioModeAsync({ - allowsRecordingIOS: true, - playsInSilentModeIOS: true + await setAudioModeAsync({ + allowsRecording: true, + playsInSilentMode: true }); - const highQuality = Audio.RecordingOptionsPresets.HIGH_QUALITY; + // Prepare and start recording with metering-enabled options + const highQuality = RecordingPresets.HIGH_QUALITY; const options = { ...highQuality, ios: { @@ -291,59 +333,24 @@ const WalkieTalkieRecorder: React.FC = ({ ...(highQuality?.android ?? {}), isMeteringEnabled: true } - } as typeof highQuality; + }; - const result = await Audio.Recording.createAsync(options); - const activeRecording = result.recording; - activeRecording.setProgressUpdateInterval(9); + await recorder.prepareToRecordAsync(options); // Check if we were cancelled during async setup (user released early) // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (shouldCancelRecordingRef.current) { - await activeRecording.stopAndUnloadAsync(); shouldCancelRecordingRef.current = false; return; } - const _duration = performance.now() - startTime; + recorder.record(); - setRecording(activeRecording); + setHasActiveRecording(true); setRecordingDuration(0); - // Track energy range for logging - const energyRange = { min: Infinity, max: -Infinity }; - - // Set up status monitoring - activeRecording.setOnRecordingStatusUpdate((status) => { - if (status.isRecording) { - const duration = status.durationMillis || 0; - setRecordingDuration(duration); - // Notify parent of duration updates for progress bar - onRecordingDurationUpdate?.(duration); - - const anyStatus = status as unknown as { metering?: number }; - let amplitude: number; - if (typeof anyStatus.metering === 'number') { - const db = anyStatus.metering; - const normalizedDb = Math.max(-60, Math.min(0, db)); - amplitude = Math.pow(10, normalizedDb / 20); - appendLiveSample(amplitude); - } else { - const t = duration / 1000; - const base = 0.3 + Math.sin(t * 24) * 0.15; - const noise = (Math.random() - 0.5) * 0.1; - amplitude = Math.max(0.02, Math.min(0.8, base + noise)); - appendLiveSample(amplitude); - } - - // Track energy range - energyRange.min = Math.min(energyRange.min, amplitude); - energyRange.max = Math.max(energyRange.max, amplitude); - } - }); - - // Store energy range ref for logging on stop - (activeRecording as RecordingWithEnergyRange)._energyRange = energyRange; + // Reset energy range for this recording + energyRangeRef.current = { min: Infinity, max: -Infinity }; } catch (error) { console.error('❌ Failed to start recording:', error); onRecordingStop(); // Clean up @@ -351,8 +358,8 @@ const WalkieTalkieRecorder: React.FC = ({ }; const stopRecording = async () => { - if (!recording) { - // Recording object not created yet (user released during async setup) + if (!hasActiveRecording) { + // Recording not started yet (user released during async setup) // Set cancellation flag so startRecording() knows to abort shouldCancelRecordingRef.current = true; onRecordingStop(); @@ -360,16 +367,8 @@ const WalkieTalkieRecorder: React.FC = ({ } try { - const status = await recording.getStatusAsync().catch(() => null); - if (!status) { - console.warn('⚠️ Recording no longer exists, skipping stop'); - setRecording(null); - onRecordingStop(); - return; - } - - await recording.stopAndUnloadAsync(); - const uri = recording.getURI(); + await recorder.stop(); + const uri = recorder.uri; if (uri) { if (recordingDuration >= MIN_RECORDING_DURATION) { @@ -380,14 +379,14 @@ const WalkieTalkieRecorder: React.FC = ({ } } - setRecording(null); + setHasActiveRecording(false); setRecordingDuration(0); setRecordedSamples([]); onRecordingStop(); } catch (error) { console.error('Failed to stop recording:', error); - setRecording(null); + setHasActiveRecording(false); setRecordingDuration(0); setRecordedSamples([]); onRecordingStop(); diff --git a/components/navigation/TabBarIcon.tsx b/components/navigation/TabBarIcon.tsx index a4892dacd..c14ecbe02 100644 --- a/components/navigation/TabBarIcon.tsx +++ b/components/navigation/TabBarIcon.tsx @@ -1,12 +1,21 @@ -// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/ +// You can explore Lucide icons at https://lucide.dev/ -import Ionicons from '@expo/vector-icons/Ionicons'; -import type { IconProps } from '@expo/vector-icons/build/createIconSet'; -import type { ComponentProps } from 'react'; +import { Icon } from '@/components/ui/icon'; +import type { LucideIcon, LucideProps } from 'lucide-react-native'; +import type { StyleProp, ViewStyle } from 'react-native'; export function TabBarIcon({ + as, style, ...rest -}: IconProps['name']>) { - return ; +}: { as: LucideIcon; style?: StyleProp } & LucideProps) { + return ( + + ); } diff --git a/components/questsComponents/QuestItem.tsx b/components/questsComponents/QuestItem.tsx index 3a9919536..25f29ed14 100644 --- a/components/questsComponents/QuestItem.tsx +++ b/components/questsComponents/QuestItem.tsx @@ -1,9 +1,9 @@ import { QuestCard } from '@/components/QuestCard'; +import { Button } from '@/components/ui/button'; import type { Project } from '@/database_services/projectService'; import type { Quest } from '@/database_services/questService'; import type { Tag } from '@/database_services/tagService'; import React, { useCallback } from 'react'; -import { TouchableOpacity } from 'react-native'; // Memoized quest item component for better performance @@ -24,9 +24,9 @@ export const QuestItem = React.memo( if (!project) return null; return ( - + ); } ); diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 6a298e70b..9e5b2fcc8 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -1,25 +1,35 @@ import { TextClassContext } from '@/components/ui/text'; +import { easeButton, easeOut } from '@/constants/animations'; import { cn, useThemeColor } from '@/utils/styleUtils'; import type { VariantProps } from 'class-variance-authority'; import { cva } from 'class-variance-authority'; -import type { BaseSyntheticEvent } from 'react'; import * as React from 'react'; -import { ActivityIndicator, Pressable, TouchableOpacity } from 'react-native'; +import type { GestureResponderEvent, Pressable } from 'react-native'; +import { ActivityIndicator, Pressable as RNPressable } from 'react-native'; +import Animated, { + useAnimatedReaction, + useAnimatedStyle, + useSharedValue, + withTiming +} from 'react-native-reanimated'; import * as Slot from './slot'; +const AnimatedPressable = Animated.createAnimatedComponent(RNPressable); + const buttonVariants = cva( - 'group flex items-center justify-center rounded-md web:ring-offset-background web:transition-[transform,color] web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2', + 'group flex items-center justify-center rounded-md web:ring-offset-background web:transition-[color] web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2', { variants: { variant: { - default: 'bg-primary active:opacity-90 web:hover:opacity-90', - destructive: 'bg-destructive active:opacity-90 web:hover:opacity-90', + default: 'bg-primary web:hover:opacity-90', + destructive: 'bg-destructive web:hover:opacity-90', outline: 'border border-input bg-background active:bg-accent web:hover:bg-accent web:hover:text-accent-foreground', - secondary: 'bg-secondary active:opacity-80 web:hover:opacity-80', + secondary: 'bg-secondary web:hover:opacity-80', ghost: 'active:bg-accent web:hover:bg-accent web:hover:text-accent-foreground', - link: 'active:scale-100 web:underline-offset-4 web:hover:underline web:focus:underline' + link: 'web:underline-offset-4 web:hover:underline web:focus:underline', + plain: '' }, size: { sm: 'h-10 rounded-md px-3', @@ -29,7 +39,8 @@ const buttonVariants = cva( icon: 'size-10', 'icon-lg': 'size-12', 'icon-xl': 'size-14', - 'icon-2xl': 'size-16' + 'icon-2xl': 'size-16', + auto: '' } }, defaultVariants: { @@ -50,7 +61,8 @@ const buttonTextVariants = cva( secondary: 'text-secondary-foreground group-active:text-secondary-foreground', ghost: 'group-active:text-accent-foreground', - link: 'text-primary group-active:underline' + link: 'text-primary group-active:underline', + plain: 'text-primary' }, size: { default: '', @@ -60,7 +72,8 @@ const buttonTextVariants = cva( icon: '', 'icon-lg': '', 'icon-xl': '', - 'icon-2xl': '' + 'icon-2xl': '', + auto: '' } }, defaultVariants: { @@ -70,18 +83,174 @@ const buttonTextVariants = cva( } ); -const ButtonPressable = Pressable; +type ButtonPressableComponentProps = React.ComponentPropsWithoutRef< + typeof RNPressable +> & { + ref?: React.ComponentRef; + disabled?: boolean; + className?: string; +}; + +/** + * ButtonPressable - A Pressable component with built-in scale animation on press + * Handles scale animation on press (0.97 scale) with smooth transitions + * Uses react-native-reanimated on all platforms (native and web) + */ +const ButtonPressable = React.forwardRef< + React.ComponentRef, + ButtonPressableComponentProps +>(({ disabled, onPressIn, onPressOut, style, className, ...props }, ref) => { + const scale = useSharedValue(1); + const opacity = useSharedValue(disabled ? 0.5 : 1); + + useAnimatedReaction( + () => disabled, + (isDisabled) => { + opacity.set( + withTiming(isDisabled ? 0.5 : 1, { + duration: 160, + easing: easeButton + }) + ); + } + ); + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ scale: scale.get() }], + opacity: opacity.get() + })); + + return ( + { + if (!disabled) { + scale.set( + withTiming(0.97, { + duration: 160, + easing: easeButton + }) + ); + opacity.set( + withTiming(0.9, { + duration: 160, + easing: easeButton + }) + ); + } + onPressIn?.(e); + }} + onPressOut={(e: GestureResponderEvent) => { + if (!disabled) { + scale.set( + withTiming(1, { + duration: 160, + easing: easeButton + }) + ); + opacity.set( + withTiming(1, { + duration: 160, + easing: easeButton + }) + ); + } + onPressOut?.(e); + }} + style={[animatedStyle, style]} + className={className} + {...props} + /> + ); +}); -const ButtonPressableOpacity = TouchableOpacity; +ButtonPressable.displayName = 'ButtonPressable'; -type ButtonPressableProps = Omit< - React.ComponentPropsWithoutRef, - 'onPress' +type PressableOpacityProps = React.ComponentPropsWithoutRef< + typeof RNPressable > & { + ref?: React.ComponentRef; + disabled?: boolean; + activeOpacity?: number; + className?: string; +}; + +/** + * PressableOpacity - A Pressable component with opacity animation on press + * Similar to TouchableOpacity but uses react-native-reanimated for better performance + * Uses opacity animation (default 0.7) on press with smooth transitions + */ +const OpacityPressable = React.forwardRef< + React.ComponentRef, + PressableOpacityProps +>( + ( + { + disabled, + activeOpacity = 0.4, // official TouchableOpacity default is 0.2 + onPressIn, + onPressOut, + style, + className, + ...props + }, + ref + ) => { + const opacity = useSharedValue(disabled ? 0.5 : 1); + + useAnimatedReaction( + () => disabled, + (isDisabled) => { + opacity.set( + withTiming(isDisabled ? 0.5 : 1, { + duration: 160, + easing: easeOut + }) + ); + } + ); + + const animatedStyle = useAnimatedStyle(() => ({ + opacity: opacity.get() + })); + + return ( + { + opacity.set( + withTiming(activeOpacity, { + duration: 160, + easing: easeOut + }) + ); + onPressIn?.(e); + }} + onPressOut={(e: GestureResponderEvent) => { + opacity.set( + withTiming(1, { + duration: 160, + easing: easeOut + }) + ); + onPressOut?.(e); + }} + style={[animatedStyle, style]} + className={className} + {...props} + /> + ); + } +); + +OpacityPressable.displayName = 'OpacityPressable'; + +type ButtonPressableProps = React.ComponentPropsWithoutRef & { ref?: React.ComponentRef; role?: string; disabled?: boolean; - onPress?: (e?: BaseSyntheticEvent) => void | Promise; }; type ButtonProps = ButtonPressableProps & @@ -104,6 +273,8 @@ const Button = React.forwardRef< loading, disabled, asChild, + onPressIn: _onPressIn, + onPressOut: _onPressOut, ...props }: ButtonProps, ref @@ -119,7 +290,8 @@ const Button = React.forwardRef< secondary: secondaryForeground, outline: accentForeground, ghost: accentForeground, - link: primaryForeground + link: primaryForeground, + plain: primaryForeground } as const; const isDisabled = disabled || loading; @@ -140,34 +312,35 @@ const Button = React.forwardRef< ); - const commonProps = { - className: cn( - 'flex flex-row items-center gap-2', - isDisabled && 'opacity-50 web:pointer-events-none web:cursor-default', - buttonVariants({ variant, size, className }) - ), - role: 'button' as const, - disabled: isDisabled, - ...props - }; + // Use PressableOpacity for link and plain variants (subtle opacity animation) + // Use ButtonPressable for other variants (scale animation) + const Component = asChild + ? Slot.Pressable + : variant === 'link' || variant === 'plain' + ? OpacityPressable + : ButtonPressable; return ( - {asChild ? ( - - {content} - - ) : ( - - {content} - - )} + + {content} + ); } @@ -178,8 +351,8 @@ Button.displayName = 'Button'; export { Button, ButtonPressable, - ButtonPressableOpacity, buttonTextVariants, - buttonVariants + buttonVariants, + OpacityPressable }; export type { ButtonProps }; diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx index 59264b75f..cb041c413 100644 --- a/components/ui/checkbox.tsx +++ b/components/ui/checkbox.tsx @@ -11,12 +11,14 @@ function Checkbox({ checkedClassName, indicatorClassName, iconClassName, + testID = 'checkbox', ...props }: CheckboxPrimitive.RootProps & React.RefAttributes & { checkedClassName?: string; indicatorClassName?: string; iconClassName?: string; + testID?: string; }) { return ( | null; @@ -234,6 +233,10 @@ const DrawerContent = React.forwardRef< return ( ( ( - KeyboardAwareScrollView - ); +const AnimatedScrollView = Animated.createAnimatedComponent( + KeyboardAwareScrollView +); const BottomSheetScrollViewComponent = createBottomSheetScrollableComponent< BottomSheetScrollViewMethods, BottomSheetScrollViewProps diff --git a/components/ui/text.tsx b/components/ui/text.tsx index c2411d9a1..b6208cb36 100644 --- a/components/ui/text.tsx +++ b/components/ui/text.tsx @@ -45,7 +45,7 @@ const textVariants = cva( } }, defaultVariants: { - variant: 'default' + variant: 'default' // this is the default variant } } ); @@ -74,8 +74,8 @@ const TextClassContext = React.createContext(undefined); function Text({ className, - asChild = false, - variant = 'default', + asChild, + variant, ...props }: React.ComponentProps & TextVariantProps & @@ -87,22 +87,12 @@ function Text({ const { style, ...restProps } = props; const notoSansStyle = useNotoSans(mergedClassName, style); + const Component = asChild ? Slot.Text : RNText; // Render directly as RNText to avoid Slot navigation context issues during transitions // Only use Slot.Text when explicitly using asChild pattern - if (!asChild) { - return ( - - ); - } return ( - Promise; - playSoundSequence: (uris: string[], audioId?: string) => Promise; + playSound: (input: PlayInput, audioId?: string) => Promise; + /** @deprecated Use playSound — it now accepts all input types */ + playSoundSequence: ( + segments: (string | AudioSegment)[], + audioId?: string + ) => Promise; stopCurrentSound: () => Promise; + waitForPlaybackEnd: (audioId: string) => Promise; isPlaying: boolean; currentAudioId: string | null; position: number; // Keep for backward compatibility duration: number; // Keep for backward compatibility setPosition: (position: number) => Promise; - // NEW: SharedValues for high-performance UI updates + // SharedValues for high-performance UI updates (60fps via Reanimated) positionShared: SharedValue; durationShared: SharedValue; } @@ -20,286 +58,626 @@ interface AudioContextType { const AudioContext = createContext(undefined); export function AudioProvider({ children }: { children: React.ReactNode }) { + // Hybrid progress model: + // - Get duration for a segment or segment sequence + // and use that to set the duration for audio progress bar animation + // - sparse native status callbacks for truth/corrections + // - continuous UI-thread timing for smooth movement + const DRIFT_CORRECTION_THRESHOLD_MS = 80; + // Initial merged-playback timing estimate: add this much wall-clock time + // per segment before we do the one-time last-segment slope correction. + const MERGED_SEGMENT_PADDING_MS = 250; + const FINAL_SEGMENT_END_FUDGE_MS = 40; + // How often to push React state (triggers re-renders). Keep low to reduce + // JS-thread pressure — progress bars use SharedValues, not React state. + const REACT_STATE_THROTTLE_MS = 100; + const [isPlaying, setIsPlaying] = useState(false); const [currentAudioId, setCurrentAudioId] = useState(null); const [position, setPositionState] = useState(0); const [duration, setDuration] = useState(0); - // NEW: Reanimated SharedValues for high-performance position tracking + // Reanimated SharedValues for high-performance position tracking const positionShared = useSharedValue(0); const durationShared = useSharedValue(0); - const cumulativePositionShared = useSharedValue(0); // SharedValue for worklet access - - const soundRef = useRef(null); - const positionUpdateInterval = useRef | null>( - null + const cumulativePositionShared = useSharedValue(0); + + const soundRef = useRef(null); + const soundListenerRef = useRef<{ remove: () => void } | null>(null); + const lastReactStateUpdateMs = useRef(0); + const currentAudioIdRef = useRef(null); + const isPlayingRef = useRef(false); + const isAdvancingSegmentRef = useRef(false); + const useSequenceLevelProgressRef = useRef(false); + const hasRetimedLastSegmentRef = useRef(false); + const playbackSessionRef = useRef(0); + const preloadedSegmentRef = useRef<{ + index: number; + sound: AudioPlayer; + } | null>(null); + const animationStartWallClockMsRef = useRef(null); + const animationStartPositionMsRef = useRef(0); + const animationTargetPositionMsRef = useRef(0); + const animationDurationMsRef = useRef(0); + // Wall-clock guard: ignore all status-callback position updates until this + // timestamp. expo-av fires an unpredictable number of eager callbacks when + // a sound loads/plays/seeks — not on the interval timer. Those early + // callbacks carry real positions that, if applied, would hard-set + // positionShared and interrupt the deterministic withTiming animation, + // causing visible jumps at the very start of playback. + const statusGuardUntilMsRef = useRef(0); + const playbackCompletionWaitersRef = useRef void>>>( + new Map() ); - const sequenceQueue = useRef([]); + const sequenceSegments = useRef([]); const currentSequenceIndex = useRef(0); - const segmentDurations = useRef([]); // Track duration of each segment - // Use SharedValue instead of ref for worklet access (prevents serialization warnings) - const isTrackingPosition = useSharedValue(false); + const segmentDurations = useRef([]); // Effective (trimmed) duration per segment // Store SharedValues in refs to satisfy React Compiler (they're stable references) const positionSharedRef = useRef(positionShared); const durationSharedRef = useRef(durationShared); const cumulativePositionSharedRef = useRef(cumulativePositionShared); - const isTrackingPositionRef = useRef(isTrackingPosition); - // Clear the position update interval - const clearPositionInterval = () => { - if (positionUpdateInterval.current) { - clearInterval(positionUpdateInterval.current); - positionUpdateInterval.current = null; - } + const setCurrentAudioIdState = (audioId: string | null) => { + currentAudioIdRef.current = audioId; + setCurrentAudioId(audioId); }; - // Start updating position at regular intervals when playing - const startPositionTracking = () => { - clearPositionInterval(); - isTrackingPositionRef.current.value = true; + const setIsPlayingState = (playing: boolean) => { + isPlayingRef.current = playing; + setIsPlaying(playing); + }; - positionUpdateInterval.current = setInterval(() => { - if (soundRef.current) { - void soundRef.current.getStatusAsync().then((status) => { - if (status.isLoaded) { - // Calculate cumulative position across all segments - let cumulativeDuration = 0; - for (let i = 0; i < currentSequenceIndex.current; i++) { - cumulativeDuration += segmentDurations.current[i] || 0; - } - const totalPosition = cumulativeDuration + status.positionMillis; - cumulativePositionSharedRef.current.value = totalPosition; // Update SharedValue - setPositionState(totalPosition); - } - }); + // expo-audio players load asynchronously; wait until duration/position are usable. + const waitForPlayerLoaded = async (player: AudioPlayer): Promise => { + if (player.isLoaded) return; + await new Promise((resolve) => { + const timeoutId = setTimeout(resolve, 5000); + const checkId = setInterval(() => { + if (!player.isLoaded) return; + clearInterval(checkId); + clearTimeout(timeoutId); + resolve(); + }, 10); + }); + }; + + const resolvePlaybackWaiters = (audioId: string | null) => { + if (!audioId) return; + const waiters = playbackCompletionWaitersRef.current.get(audioId); + if (!waiters || waiters.size === 0) return; + playbackCompletionWaitersRef.current.delete(audioId); + waiters.forEach((resolve) => { + try { + resolve(); + } catch { + // Ignore waiter errors } - }, 100); // Update every 100ms for smoother animation + }); }; - // NEW: Frame callback for high-performance SharedValue updates - // This runs on the UI thread at 60fps for buttery smooth progress bars - useFrameCallback(() => { - 'worklet'; - // Only update if actively tracking position - // The isTrackingPosition check prevents updates after cleanup - if (!isTrackingPosition.value) { + const waitForPlaybackEnd = async (audioId: string) => { + if (!audioId) return; + if (currentAudioIdRef.current !== audioId || !isPlayingRef.current) { return; } - // Copy cumulative position to the main position SharedValue - // This runs at 60fps on UI thread for buttery smooth progress bars! - positionShared.value = cumulativePositionShared.value; - }); + await new Promise((resolve) => { + const map = playbackCompletionWaitersRef.current; + const existing = map.get(audioId); + if (existing) { + existing.add(resolve); + } else { + map.set(audioId, new Set([resolve])); + } + }); + }; + + const clearStatusListener = () => { + soundListenerRef.current?.remove(); + soundListenerRef.current = null; + }; + + const resetPredictedProgress = () => { + animationStartWallClockMsRef.current = null; + animationStartPositionMsRef.current = 0; + animationTargetPositionMsRef.current = 0; + animationDurationMsRef.current = 0; + statusGuardUntilMsRef.current = 0; + }; + + const cumulativeDurationBeforeCurrent = () => { + let cumulativeDuration = 0; + for (let i = 0; i < currentSequenceIndex.current; i++) { + cumulativeDuration += segmentDurations.current[i] || 0; + } + return cumulativeDuration; + }; + + const totalSequenceDuration = () => + segmentDurations.current.reduce((sum, d) => sum + d, 0); + + const unloadPreloadedSegment = async () => { + const preloaded = preloadedSegmentRef.current; + preloadedSegmentRef.current = null; + if (!preloaded) return; + try { + preloaded.sound.pause(); + await Promise.resolve(preloaded.sound.release()); + } catch { + // Ignore preload unload errors + } + }; + + const preloadSegmentAtIndex = async (index: number, sessionId: number) => { + if (!useSequenceLevelProgressRef.current) return; + if (index < 0 || index >= sequenceSegments.current.length) return; + if (sessionId !== playbackSessionRef.current) return; + + const existing = preloadedSegmentRef.current; + if (existing?.index === index) return; + + await unloadPreloadedSegment(); + if (sessionId !== playbackSessionRef.current) return; + + const segment = sequenceSegments.current[index]; + if (!segment) return; + + try { + const sound = createAudioPlayer(segment.uri); + await waitForPlayerLoaded(sound); + + const shouldSeek = segment.startMs != null && segment.startMs > 0; + if (shouldSeek) { + await sound.seekTo(segment.startMs! / 1000); + } + + if (sessionId !== playbackSessionRef.current) { + try { + void sound.release(); + } catch { + // Ignore stale preload unload errors + } + return; + } + + preloadedSegmentRef.current = { index, sound }; + } catch { + // Preload failures are non-fatal; segment will load on demand. + } + }; + + const startPredictedProgressAnimation = ( + fromTotalPosition: number, + targetTotalPosition: number, + durationOverrideMs?: number + ) => { + const from = Math.max(0, fromTotalPosition); + const target = Math.max(from, targetTotalPosition); + const distance = target - from; + const duration = + durationOverrideMs == null + ? distance + : Math.max(0, Math.round(durationOverrideMs)); + + animationStartWallClockMsRef.current = Date.now(); + animationStartPositionMsRef.current = from; + animationTargetPositionMsRef.current = target; + animationDurationMsRef.current = duration; + + cumulativePositionSharedRef.current.value = from; + positionSharedRef.current.value = from; + + if (distance <= 0 || duration <= 0) return; + + positionSharedRef.current.value = withTiming(target, { + duration, + easing: Easing.linear + }); + }; + + const predictedPositionNow = () => { + const startedAt = animationStartWallClockMsRef.current; + if (!startedAt) return null; + const elapsed = Date.now() - startedAt; + const duration = animationDurationMsRef.current; + const from = animationStartPositionMsRef.current; + const target = animationTargetPositionMsRef.current; + const distance = target - from; + if (duration <= 0 || distance <= 0) return target; + const progress = Math.max(0, Math.min(1, elapsed / duration)); + return from + distance * progress; + }; const stopCurrentSound = async () => { - clearPositionInterval(); - // Update SharedValue via ref to satisfy React Compiler - isTrackingPositionRef.current.value = false; + playbackSessionRef.current += 1; + const finishingAudioId = currentAudioIdRef.current; + clearStatusListener(); + isAdvancingSegmentRef.current = false; + useSequenceLevelProgressRef.current = false; + hasRetimedLastSegmentRef.current = false; + resetPredictedProgress(); // Reset sequence state - sequenceQueue.current = []; + sequenceSegments.current = []; currentSequenceIndex.current = 0; segmentDurations.current = []; - // Update SharedValue via ref to satisfy React Compiler cumulativePositionSharedRef.current.value = 0; if (soundRef.current) { - await soundRef.current.stopAsync(); - await soundRef.current.unloadAsync(); + soundRef.current.pause(); + soundRef.current.release(); soundRef.current = null; } + await unloadPreloadedSegment(); - // Always reset state, even if soundRef.current is null - // This fixes the issue where pause state gets stuck - setIsPlaying(false); - setCurrentAudioId(null); + setIsPlayingState(false); + setCurrentAudioIdState(null); setPositionState(0); setDuration(0); - // Reset SharedValues via refs to satisfy React Compiler positionSharedRef.current.value = 0; durationSharedRef.current.value = 0; + resolvePlaybackWaiters(finishingAudioId); }; const setPosition = async (newPosition: number) => { if (soundRef.current && isPlaying) { - await soundRef.current.setPositionAsync(newPosition); + await soundRef.current.seekTo(newPosition / 1000); setPositionState(newPosition); - // Update SharedValues via refs to satisfy React Compiler cumulativePositionSharedRef.current.value = newPosition; positionSharedRef.current.value = newPosition; + resetPredictedProgress(); } }; const playNextInSequence = async () => { currentSequenceIndex.current++; - if (currentSequenceIndex.current < sequenceQueue.current.length) { - const nextUri = sequenceQueue.current[currentSequenceIndex.current]; - if (nextUri) { - await playSound(nextUri, currentAudioId || undefined, true); + if (currentSequenceIndex.current < sequenceSegments.current.length) { + const nextSeg = sequenceSegments.current[currentSequenceIndex.current]; + if (nextSeg) { + await playSegment(nextSeg, currentAudioId || undefined); } } else { - // Sequence finished - reset all state - sequenceQueue.current = []; + // Sequence finished — reset all state + playbackSessionRef.current += 1; + isAdvancingSegmentRef.current = false; + useSequenceLevelProgressRef.current = false; + hasRetimedLastSegmentRef.current = false; + await unloadPreloadedSegment(); + resetPredictedProgress(); + sequenceSegments.current = []; currentSequenceIndex.current = 0; segmentDurations.current = []; - // Reset SharedValues via refs to satisfy React Compiler cumulativePositionSharedRef.current.value = 0; positionSharedRef.current.value = 0; durationSharedRef.current.value = 0; - isTrackingPositionRef.current.value = false; - setIsPlaying(false); - setCurrentAudioId(null); + setIsPlayingState(false); + resolvePlaybackWaiters(currentAudioIdRef.current); + setCurrentAudioIdState(null); setPositionState(0); setDuration(0); - clearPositionInterval(); } }; - const playSound = async ( - uri: string, - audioId?: string, - isSequencePart = false - ) => { - // Stop current sound if any is playing (but preserve sequence state) + /** + * Load and play a single segment. Handles optional startMs (seek before + * playing) and optional endMs (enforced by startPositionTracking). + * Used internally by playSound and playNextInSequence. + */ + const playSegment = async (segment: AudioSegment, audioId?: string) => { + // Stop current sound if any is playing if (soundRef.current) { - clearPositionInterval(); - - // Store the duration of the previous segment if part of a sequence - if (isSequencePart && currentSequenceIndex.current > 0) { - const prevStatus = await soundRef.current.getStatusAsync(); - if (prevStatus.isLoaded) { - segmentDurations.current[currentSequenceIndex.current - 1] = - prevStatus.durationMillis ?? 0; - } - } - - await soundRef.current.stopAsync(); - await soundRef.current.unloadAsync(); + clearStatusListener(); + soundRef.current.pause(); + soundRef.current.release(); soundRef.current = null; } + isAdvancingSegmentRef.current = false; // Set up audio mode - await Audio.setAudioModeAsync({ - allowsRecordingIOS: false, - playsInSilentModeIOS: true, - staysActiveInBackground: true + await setAudioModeAsync({ + allowsRecording: false, + playsInSilentMode: true, + shouldPlayInBackground: true }); - // Load and play new sound + const shouldSeek = segment.startMs != null && segment.startMs > 0; + const segmentIndex = currentSequenceIndex.current; + const preloaded = preloadedSegmentRef.current; + const canUsePreloaded = preloaded?.index === segmentIndex; + let lastSegmentRemainingMsAtStart: number | null = null; + try { - const { sound } = await Audio.Sound.createAsync( - { uri }, - { shouldPlay: true } - ); + let sound: AudioPlayer; + if (canUsePreloaded) { + sound = preloaded.sound; + preloadedSegmentRef.current = null; + } else { + sound = createAudioPlayer(segment.uri); + await waitForPlayerLoaded(sound); + } soundRef.current = sound; - setIsPlaying(true); + setIsPlayingState(true); if (audioId) { - setCurrentAudioId(audioId); + setCurrentAudioIdState(audioId); } - // Get initial status to set the duration - const status = await sound.getStatusAsync(); - if (status.isLoaded) { - const durationMs = status.durationMillis ?? 0; - - // Store this segment's duration - segmentDurations.current[currentSequenceIndex.current] = durationMs; + // Seek to startMs before starting playback (unless preloaded segment + // was already seeked during background preload). + if (shouldSeek && !canUsePreloaded) { + await sound.seekTo(segment.startMs! / 1000); + } - // Calculate total duration across all segments - const totalDuration = segmentDurations.current.reduce( - (sum, d) => sum + d, - 0 - ); - setDuration(totalDuration); - durationSharedRef.current.value = totalDuration; // Update SharedValue + // Get initial status to set the duration + if (sound.isLoaded) { + const fullDurationMs = sound.duration * 1000; + + // Effective duration for this segment (respecting start/end) + const startMs = segment.startMs ?? 0; + const endMs = segment.endMs ?? fullDurationMs; + const effectiveDuration = Math.max(0, endMs - startMs); + + // Store this segment's effective duration + segmentDurations.current[currentSequenceIndex.current] = + effectiveDuration; + + // Single-segment playback animates per segment. + // Merged sequences start from playSound() and keep a single timeline + // (no per-segment retiming). + if (!useSequenceLevelProgressRef.current) { + const totalDuration = totalSequenceDuration(); + setDuration(totalDuration); + durationSharedRef.current.value = totalDuration; + const cumulativeBefore = cumulativeDurationBeforeCurrent(); + const targetTotal = cumulativeBefore + effectiveDuration; + startPredictedProgressAnimation(cumulativeBefore, targetTotal); + } else { + const isLastSegment = + currentSequenceIndex.current === + sequenceSegments.current.length - 1; + if (isLastSegment && !hasRetimedLastSegmentRef.current) { + const cumulativeBefore = cumulativeDurationBeforeCurrent(); + const segStartMs = segment.startMs ?? 0; + const playedInSegmentAtStart = Math.max( + 0, + sound.currentTime * 1000 - segStartMs + ); + const playedTotalAtStart = + cumulativeBefore + playedInSegmentAtStart; + const remainingTotal = + totalSequenceDuration() - + playedTotalAtStart + + FINAL_SEGMENT_END_FUDGE_MS; + lastSegmentRemainingMsAtStart = Math.max(0, remainingTotal); + } + } + // Guard: block all status-callback position writes for 1 second. + // This lets the deterministic withTiming animation run uninterrupted + // through the startup burst of eager expo-av callbacks. + // animationStartWallClockMsRef was just set by the call above. + statusGuardUntilMsRef.current = + (animationStartWallClockMsRef.current ?? 0) + 1000; } - // Start tracking position - startPositionTracking(); + // Set up listener for position updates + natural completion. + // Track whether the player has actually started playing, used as a guard + // so that the !status.playing fallback doesn't fire on initial load. + let hasStartedPlaying = false; + soundListenerRef.current = sound.addListener( + 'playbackStatusUpdate', + (status) => { + if (!status.isLoaded) { + return; + } - // Set up listener for when sound finishes playing - sound.setOnPlaybackStatusUpdate((status) => { - if (!status.isLoaded) { - return; - } + if (status.playing) hasStartedPlaying = true; + + const now = Date.now(); + const guarded = now < statusGuardUntilMsRef.current; + const currentSeg = + sequenceSegments.current[currentSequenceIndex.current]; + const cumulativeDuration = cumulativeDurationBeforeCurrent(); + const segStartMs = currentSeg?.startMs ?? 0; + const currentPositionMs = status.currentTime * 1000; + const positionInSegment = currentPositionMs - segStartMs; + const totalPosition = + cumulativeDuration + Math.max(0, positionInSegment); + + // While the guard is active, skip all position writes and drift + // correction. The deterministic withTiming animation is running + // smoothly on the UI thread and must not be interrupted by + // real-position hard-sets from these early callbacks. + if (!guarded) { + cumulativePositionSharedRef.current.value = totalPosition; + if ( + now - lastReactStateUpdateMs.current >= + REACT_STATE_THROTTLE_MS + ) { + lastReactStateUpdateMs.current = now; + setPositionState(totalPosition); + } - if (status.didJustFinish) { - clearPositionInterval(); - isTrackingPositionRef.current.value = false; - void sound.unloadAsync(); - soundRef.current = null; + // For merged playback, keep one continuous timeline and do not + // restart timing at segment boundaries. + if (!useSequenceLevelProgressRef.current) { + const currentTargetTotal = + cumulativeDuration + + (segmentDurations.current[currentSequenceIndex.current] || 0); + const predicted = predictedPositionNow(); + const drift = + predicted == null + ? Number.POSITIVE_INFINITY + : totalPosition - predicted; + if (Math.abs(drift) >= DRIFT_CORRECTION_THRESHOLD_MS) { + startPredictedProgressAnimation( + totalPosition, + currentTargetTotal + ); + } + } + } + + // Enforce endMs boundary for trimmed playback. + if ( + currentSeg?.endMs != null && + currentPositionMs >= currentSeg.endMs && + !isAdvancingSegmentRef.current + ) { + isAdvancingSegmentRef.current = true; + clearStatusListener(); + sound.pause(); + sound.release(); + if (soundRef.current === sound) { + soundRef.current = null; + } + void playNextInSequence(); + return; + } - // Check if there are more sounds in the sequence - if (sequenceQueue.current.length > 0) { + // expo-audio doesn't fire didJustFinish reliably on all platforms. + // Fallback: treat playing→false (after having started) as completion, + // since the listener is always removed before any manual pause/stop. + if (status.didJustFinish || (hasStartedPlaying && !status.playing)) { + if (isAdvancingSegmentRef.current) return; + isAdvancingSegmentRef.current = true; + clearStatusListener(); + sound.release(); + if (soundRef.current === sound) { + soundRef.current = null; + } + // playNextInSequence advances to the next segment, or resets + // all state if no segments remain. void playNextInSequence(); - } else { - // No more sounds in sequence - setIsPlaying(false); - setCurrentAudioId(null); - setPositionState(0); - positionSharedRef.current.value = 0; - durationSharedRef.current.value = 0; } } - }); + ); + sound.play(); + + // One-time retime: when the final segment actually starts playing, + // retime from current progress so remaining time matches that segment. + if ( + lastSegmentRemainingMsAtStart != null && + !hasRetimedLastSegmentRef.current && + useSequenceLevelProgressRef.current + ) { + const sequenceTotalDuration = totalSequenceDuration(); + const predicted = predictedPositionNow(); + const fromPosition = Math.max( + cumulativeDurationBeforeCurrent(), + predicted ?? positionSharedRef.current.value + ); + if (sequenceTotalDuration > fromPosition) { + // Keep the progress scale fixed and only adjust slope. + startPredictedProgressAnimation( + fromPosition, + sequenceTotalDuration, + lastSegmentRemainingMsAtStart + ); + } + hasRetimedLastSegmentRef.current = true; + } + + // Preload exactly one segment ahead while current segment is playing. + if (useSequenceLevelProgressRef.current) { + const currentSession = playbackSessionRef.current; + const nextIndex = segmentIndex + 1; + void preloadSegmentAtIndex(nextIndex, currentSession); + } } catch { - setIsPlaying(false); - setCurrentAudioId(null); + setIsPlayingState(false); + setCurrentAudioIdState(null); } }; - const playSoundSequence = async (uris: string[], audioId?: string) => { - if (uris.length === 0) return; + /** + * Unified playback function. Accepts any combination of URIs and segments: + * playSound("file.m4a", audioId) + * playSound(["a.m4a", "b.m4a"], audioId) + * playSound({ uri: "file.m4a", startMs: 500, endMs: 3000 }, audioId) + * playSound([seg1, seg2], audioId) + */ + const playSound = async (input: PlayInput, audioId?: string) => { + // Normalize any input shape to AudioSegment[] + const raw = Array.isArray(input) ? input : [input]; + const segments: AudioSegment[] = raw.map((s) => + typeof s === 'string' ? { uri: s } : s + ); + if (segments.length === 0) return; // Stop any current playback await stopCurrentSound(); + playbackSessionRef.current += 1; - // Set up sequence - sequenceQueue.current = uris; + // Always set up sequence state — even for single segments — so that + // trim enforcement (startMs/endMs) works via startPositionTracking. + sequenceSegments.current = segments; currentSequenceIndex.current = 0; - segmentDurations.current = new Array(uris.length).fill(0); - // Update SharedValue via ref to satisfy React Compiler + segmentDurations.current = new Array(segments.length).fill(0); cumulativePositionSharedRef.current.value = 0; + useSequenceLevelProgressRef.current = segments.length > 1; + hasRetimedLastSegmentRef.current = false; + + // For multi-segment sequences, preload durations for accurate total display. + // Single segments get their duration set inside playSegment. + // NOTE: Process sequentially to avoid ExoPlayer threading issues on Android. + if (segments.length > 1) { + try { + for (let index = 0; index < segments.length; index++) { + const seg = segments[index]!; + const sound = createAudioPlayer(seg.uri); + await waitForPlayerLoaded(sound); + if (sound.isLoaded) { + const fullMs = sound.duration * 1000; + const startMs = seg.startMs ?? 0; + const endMs = seg.endMs ?? fullMs; + segmentDurations.current[index] = Math.max(0, endMs - startMs); + } + sound.release(); + } - // Preload all segment durations for accurate total duration - // NOTE: Process sequentially to avoid ExoPlayer threading issues on Android - // (ExoPlayer requires all player operations on the main thread) - try { - for (let index = 0; index < uris.length; index++) { - const uri = uris[index]!; - const { sound } = await Audio.Sound.createAsync({ uri }); - const status = await sound.getStatusAsync(); - await sound.unloadAsync(); - if (status.isLoaded) { - segmentDurations.current[index] = status.durationMillis ?? 0; + const totalDuration = totalSequenceDuration(); + const initialEstimatedWallClockMs = + totalDuration + segments.length * MERGED_SEGMENT_PADDING_MS; + setDuration(totalDuration); + durationSharedRef.current.value = totalDuration; + if (totalDuration > 0) { + startPredictedProgressAnimation( + 0, + totalDuration, + initialEstimatedWallClockMs + ); } + } catch { + // Failed to preload durations, will calculate on the fly + useSequenceLevelProgressRef.current = false; } - - const totalDuration = segmentDurations.current.reduce( - (sum, d) => sum + d, - 0 - ); - setDuration(totalDuration); - durationSharedRef.current.value = totalDuration; // Update SharedValue - } catch { - // Failed to preload durations, will calculate on the fly } - // Play first sound - await playSound(uris[0]!, audioId, true); + // Play first segment + await playSegment(segments[0]!, audioId); + }; + + /** @deprecated Use playSound — it now accepts all input types */ + const playSoundSequence = async ( + input: (string | AudioSegment)[], + audioId?: string + ) => { + await playSound(input, audioId); }; // Ensure cleanup when the component unmounts React.useEffect(() => { return () => { - clearPositionInterval(); + playbackSessionRef.current += 1; + resetPredictedProgress(); + clearStatusListener(); if (soundRef.current) { - void soundRef.current.unloadAsync(); + soundRef.current.release(); } + void unloadPreloadedSegment(); }; }, []); @@ -309,6 +687,7 @@ export function AudioProvider({ children }: { children: React.ReactNode }) { playSound, playSoundSequence, stopCurrentSound, + waitForPlaybackEnd, isPlaying, currentAudioId, position, @@ -326,7 +705,7 @@ export function AudioProvider({ children }: { children: React.ReactNode }) { /** * Hook to access audio playback functionality. * @param options - Configuration options - * @param options.stopOnUnmount - Whether to stop audio when component unmounts (default: true) + * @param options.stopOnUnmount - Whether to stop audio when component unmounts (default: false) */ export function useAudio(options?: { stopOnUnmount?: boolean }) { const context = useContext(AudioContext); @@ -334,7 +713,9 @@ export function useAudio(options?: { stopOnUnmount?: boolean }) { throw new Error('useAudio must be used within an AudioProvider'); } - const stopOnUnmount = options?.stopOnUnmount ?? true; + // Default to false so transient/virtualized consumers (e.g., list cards) + // do not accidentally stop shared playback when they unmount. + const stopOnUnmount = options?.stopOnUnmount ?? false; const stopCurrentSoundRef = React.useRef(context.stopCurrentSound); // Keep ref updated with latest function diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx index e910abcef..64be070ef 100644 --- a/contexts/AuthContext.tsx +++ b/contexts/AuthContext.tsx @@ -187,7 +187,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { RNAlert.alert( 'Initialization Error', 'Failed to initialize the app. Please try logging out and back in.', - [{ text: 'OK' }] + [{ text: 'OK', isPreferred: true }] ); } }; diff --git a/db/seedData.json b/db/seedData.json index 9abf9438b..28cd46990 100644 --- a/db/seedData.json +++ b/db/seedData.json @@ -118,9 +118,9 @@ "creator_id": null }, { - "id": "lang-por-br", + "id": "lang-hin", "parent_id": null, - "name": "Brazilian Portuguese", + "name": "Hindi", "level": "language", "ui_ready": true, "active": true, @@ -130,9 +130,9 @@ "creator_id": null }, { - "id": "lang-tpi", + "id": "lang-mya", "parent_id": null, - "name": "Tok Pisin", + "name": "Burmese", "level": "language", "ui_ready": true, "active": true, @@ -142,9 +142,9 @@ "creator_id": null }, { - "id": "lang-ind", + "id": "lang-tha", "parent_id": null, - "name": "Indonesian", + "name": "Thai", "level": "language", "ui_ready": true, "active": true, @@ -154,9 +154,9 @@ "creator_id": null }, { - "id": "lang-nep", + "id": "lang-cmn", "parent_id": null, - "name": "Nepali", + "name": "Mandarin", "level": "language", "ui_ready": true, "active": true, @@ -233,10 +233,10 @@ "creator_id": null }, { - "id": "alias-por-br-endonym", - "subject_languoid_id": "lang-por-br", - "label_languoid_id": "lang-por-br", - "name": "Português Brasileiro", + "id": "alias-hin-endonym", + "subject_languoid_id": "lang-hin", + "label_languoid_id": "lang-hin", + "name": "हिन्दी", "alias_type": "endonym", "source_names": ["lexvo"], "active": true, @@ -246,10 +246,10 @@ "creator_id": null }, { - "id": "alias-tpi-endonym", - "subject_languoid_id": "lang-tpi", - "label_languoid_id": "lang-tpi", - "name": "Tok Pisin", + "id": "alias-mya-endonym", + "subject_languoid_id": "lang-mya", + "label_languoid_id": "lang-mya", + "name": "မြန်မာ", "alias_type": "endonym", "source_names": ["lexvo"], "active": true, @@ -259,10 +259,10 @@ "creator_id": null }, { - "id": "alias-ind-endonym", - "subject_languoid_id": "lang-ind", - "label_languoid_id": "lang-ind", - "name": "Bahasa Indonesia", + "id": "alias-tha-endonym", + "subject_languoid_id": "lang-tha", + "label_languoid_id": "lang-tha", + "name": "ไทย", "alias_type": "endonym", "source_names": ["lexvo"], "active": true, @@ -272,10 +272,10 @@ "creator_id": null }, { - "id": "alias-nep-endonym", - "subject_languoid_id": "lang-nep", - "label_languoid_id": "lang-nep", - "name": "नेपाली", + "id": "alias-cmn-endonym", + "subject_languoid_id": "lang-cmn", + "label_languoid_id": "lang-cmn", + "name": "普通话", "alias_type": "endonym", "source_names": ["lexvo"], "active": true, @@ -352,11 +352,11 @@ "creator_id": null }, { - "id": "lsrc-por-br-iso", + "id": "lsrc-hin-iso", "name": "iso639-3", "version": null, - "languoid_id": "lang-por-br", - "unique_identifier": "por", + "languoid_id": "lang-hin", + "unique_identifier": "hin", "url": null, "active": true, "download_profiles": null, @@ -365,11 +365,11 @@ "creator_id": null }, { - "id": "lsrc-tpi-iso", + "id": "lsrc-mya-iso", "name": "iso639-3", "version": null, - "languoid_id": "lang-tpi", - "unique_identifier": "tpi", + "languoid_id": "lang-mya", + "unique_identifier": "mya", "url": null, "active": true, "download_profiles": null, @@ -378,11 +378,11 @@ "creator_id": null }, { - "id": "lsrc-ind-iso", + "id": "lsrc-tha-iso", "name": "iso639-3", "version": null, - "languoid_id": "lang-ind", - "unique_identifier": "ind", + "languoid_id": "lang-tha", + "unique_identifier": "tha", "url": null, "active": true, "download_profiles": null, @@ -391,11 +391,11 @@ "creator_id": null }, { - "id": "lsrc-nep-iso", + "id": "lsrc-cmn-iso", "name": "iso639-3", "version": null, - "languoid_id": "lang-nep", - "unique_identifier": "nep", + "languoid_id": "lang-cmn", + "unique_identifier": "cmn", "url": null, "active": true, "download_profiles": null, diff --git a/db/supabase/SupabaseStorageAdapter.ts b/db/supabase/SupabaseStorageAdapter.ts index 78371ac5a..7442c8f95 100644 --- a/db/supabase/SupabaseStorageAdapter.ts +++ b/db/supabase/SupabaseStorageAdapter.ts @@ -80,13 +80,14 @@ export class SupabaseStorageAdapter implements StorageAdapter { return data; } - writeFile( + // eslint-disable-next-line @typescript-eslint/require-await + async writeFile( fileURI: string, base64Data: string, options?: { encoding?: 'utf8' | 'base64'; } - ): Promise { + ) { return writeFile(fileURI, base64Data, options); } @@ -94,7 +95,7 @@ export class SupabaseStorageAdapter implements StorageAdapter { fileURI: string, options?: { encoding?: 'utf8' | 'base64'; mediaType?: string } ): Promise { - const exists = await fileExists(fileURI); + const exists = fileExists(fileURI); if (!exists) { console.error('[STORAGE] File does not exist for upload:', fileURI); @@ -110,11 +111,9 @@ export class SupabaseStorageAdapter implements StorageAdapter { } } - async deleteFile( - uri: string, - options?: { filename?: string } - ): Promise { - await deleteIfExists(uri); + // eslint-disable-next-line @typescript-eslint/require-await + async deleteFile(uri: string, options?: { filename?: string }) { + deleteIfExists(uri); const { filename } = options ?? {}; if (!filename) { @@ -136,20 +135,23 @@ export class SupabaseStorageAdapter implements StorageAdapter { // console.debug('Deleted file from storage', data); } - fileExists(fileURI: string): Promise { + // eslint-disable-next-line @typescript-eslint/require-await + async fileExists(fileURI: string): Promise { return fileExists(fileURI); } - makeDir(uri: string): Promise { - return ensureDir(uri); + // eslint-disable-next-line @typescript-eslint/require-await + async makeDir(uri: string): Promise { + ensureDir(uri); } - copyFile(sourceUri: string, targetUri: string) { - return copyFile(sourceUri, targetUri); + // eslint-disable-next-line @typescript-eslint/require-await + async copyFile(sourceUri: string, targetUri: string): Promise { + copyFile(sourceUri, targetUri); } getUserStorageDirectory(): string { - return getDocumentDirectory() ?? ''; + return getDocumentDirectory(); } stringToArrayBuffer(str: string) { diff --git a/docs/asset-audio.md b/docs/asset-audio.md new file mode 100644 index 000000000..7625d18ae --- /dev/null +++ b/docs/asset-audio.md @@ -0,0 +1,191 @@ +# AssetAudio + +The `AssetAudio` system centralizes how the app loads, plays, previews, trims, and exports an asset's audio. It hides the complexity of merged audio (multiple audio files behind one asset) and trim points behind a small, consistent API. + +## Why this exists + +An asset's audio can be one file or several files in sequence ("merged audio"), and it may have trim points. `AssetAudio` gives you one interface and a handful of functions so you never have to think about those details. Play, visualize, trim, or export; it handles the rest. + +## The `AssetAudio` interface + +```typescript +interface AssetAudioSegment { + uri: string; // local file URI + durationMs: number; // full, untrimmed duration in ms + trim?: { + startMs: number; + endMs: number; + }; +} + +interface AssetAudio { + assetId: string; + segments: AssetAudioSegment[]; + totalDurationMs: number; // sum of effective (trimmed) durations +} +``` + +Every function in the API can work from this object. + +When trimming audio the app uses this as an in-memory working copy, updating `trim` as the user drags handles. On confirmation the trim points will be written to the database. + +## File layout + +### `services/assetAudio.ts` + +Contains all the logic in one file: + +- **`AssetAudio` interface** - the type described above. +- **`useAssetAudio()` hook** - the main entry point for components. Calls `useAudio()` internally and exposes: + - **`resolve(assetId)`** - queries the database for URIs, loads segment durations, reads trim metadata, and returns an `AssetAudio` object. + - **`play(assetId | AssetAudio)`** - accepts an asset ID string or a pre-resolved `AssetAudio` object. Resolves automatically when given a string. Respects trim points. Handles single or merged audio. + - **`stop()`** - stops playback. + - **`getWaveform(assetId, options?)`** - extracts waveform amplitudes from each segment, combines them proportionally, and slices to the trim region by default. Pass `{ ignoreTrim: true }` for the full waveform. + - **`saveTrim(assetAudio)`** - writes the object's trim points back to `asset.metadata` in the database. + - **`export(assetId)`** - concatenates segments, applies trim, and returns a path to a single `.m4a` file. + - **`isPlaying`** - `boolean`, true while audio is playing. + - **`currentAudioId`** - the asset ID currently playing, or `null` when idle. Useful for highlighting the active asset in a list. + - **`positionShared`** - Reanimated `SharedValue` tracking the current playback position in milliseconds. Updates at 60fps on the UI thread for smooth progress bars. + - **`durationShared`** - Reanimated `SharedValue` with the total playback duration in milliseconds (after trim). +- The standalone functions `resolveAssetAudio`, `getAssetWaveform`, `exportAssetAudio`, and `saveTrim` are also exported directly for use outside React components. + +### `contexts/AudioContext.tsx` + +The low-level audio engine. Manages `expo-av` sound objects, position tracking via Reanimated shared values, and sequential segment playback. `useAssetAudio` wraps this internally; most components never import `AudioContext` directly. + +### `database_services/assetService.ts` + +Owns the `AssetMetadata` interface (which includes the `trim` field). `saveTrim` in `assetAudio.ts` delegates here to persist changes. + +### `utils/audioWaveform.ts` + +Contains `extractWaveformFromFile`, which reads PCM data from a single audio file and returns amplitude bars. `getAssetWaveform` calls this per-segment and stitches the results together. + +## How to use it + +### Play an asset's audio + +```typescript +const audio = useAssetAudio(); + +// One call. Resolves URIs, applies trim, handles single or merged audio. +await audio.play(assetId); + +// Stop playback +await audio.stop(); +``` + +### Get a waveform + +```typescript +const audio = useAssetAudio(); + +// Trimmed waveform (default); what the rest of the app sees +const waveform = await audio.getWaveform(assetId, { barCount: 64 }); + +// Full waveform, ignoring trim; used in the trim UI +const fullWaveform = await audio.getWaveform(assetId, { ignoreTrim: true, barCount: 128 }); +``` + +### Export to a single file + +```typescript +const audio = useAssetAudio(); + +// Concatenates segments, applies trim, returns a path to one .m4a file +const filePath = await audio.export(assetId); +``` + +### Access playback state + +```typescript +const audio = useAssetAudio(); + +audio.isPlaying; // boolean +audio.currentAudioId; // the assetId currently playing, or null +audio.positionShared; // Reanimated SharedValue (ms), 60fps updates +audio.durationShared; // Reanimated SharedValue (ms) +``` + +### Work with the raw `AssetAudio` object + +For the trim UI or anywhere you need to inspect or modify the underlying data: + +```typescript +const audio = useAssetAudio(); + +// Get the full resolved object +const resolved = await audio.resolve(assetId); +// resolved.segments[0] → { uri: "file:///...seg1.m4a", durationMs: 4200, trim: { startMs: 200, endMs: 4000 } } +// resolved.segments[1] → { uri: "file:///...seg2.m4a", durationMs: 3100 } +// resolved.totalDurationMs → 6900 (3800 trimmed + 3100 untrimmed) +``` + +### Trim modal workflow + +The trim modal is the one place that digs into the `AssetAudio` object directly. It uses `resolve` to get the raw data, then mutates a local copy as the user drags the trim handles. + +```typescript +const audio = useAssetAudio(); +const [assetAudio, setAssetAudio] = useState(null); +const [waveform, setWaveform] = useState([]); + +// 1. Open the modal: resolve the object and load the full waveform +const open = async (assetId: string) => { + const resolved = await audio.resolve(assetId); + const fullWaveform = await audio.getWaveform(assetId, { ignoreTrim: true, barCount: 128 }); + setAssetAudio(resolved); + setWaveform(fullWaveform); +}; + +// 2. User drags trim handles: adjusts start of first segment, end of last segment +const onTrimChange = (startFraction: number, endFraction: number) => { + setAssetAudio(prev => { + if (!prev) return null; + const segments = prev.segments.map((seg, i) => { + if (i === 0) { + const startMs = Math.round(startFraction * seg.durationMs); + return { ...seg, trim: { startMs, endMs: seg.trim?.endMs ?? seg.durationMs } }; + } + if (i === segments.length - 1) { + const endMs = Math.round(endFraction * seg.durationMs); + return { ...seg, trim: { startMs: seg.trim?.startMs ?? 0, endMs } }; + } + return seg; + }); + const totalDurationMs = segments.reduce((sum, seg) => + sum + (seg.trim ? seg.trim.endMs - seg.trim.startMs : seg.durationMs), 0 + ); + return { ...prev, segments, totalDurationMs }; + }); +}; + +// 3. Play preview (debounced): plays whatever trim is on the object +const debouncedPlay = useDebouncedCallback(() => { + if (assetAudio) audio.play(assetAudio); +}, [assetAudio], 300); + +// 4. Confirm: persist the object's trim to the database +const onConfirm = async () => { + if (assetAudio) await audio.saveTrim(assetAudio); +}; +``` + +The waveform display layers three things on top of each other: + +1. **Full waveform** in a faded color (the `waveform` state, always `ignoreTrim: true`). +2. **Selection overlay** in a bright color, clipped to the trim region. Uses the same waveform data, masked by the selection bounds. +3. **Clip divider lines** at the boundaries between internal audio files. Positions come from each segment's `durationMs`, converted to fractions of the full untrimmed duration. + +### Outside of React components + +`resolveAssetAudio`, `getAssetWaveform`, `exportAssetAudio`, and `saveTrim` are plain async functions. They don't require React. Import them directly for use in background tasks, scripts, or utilities. + +```typescript +import { resolveAssetAudio, exportAssetAudio } from '@/services/assetAudio'; + +const audio = await resolveAssetAudio(assetId); +const exportedPath = await exportAssetAudio(assetId); +``` + +Only `play` and the playback state properties require the `useAssetAudio` hook, because they depend on the React audio context for managing sound objects and position tracking. diff --git a/eas.json b/eas.json index 444f92878..b39a915df 100644 --- a/eas.json +++ b/eas.json @@ -10,27 +10,25 @@ "distribution": "internal", "channel": "development", "environment": "development", - "developmentClient": true, + "developmentClient": true + }, + "development-simulator": { + "extends": "development", + "withoutCredentials": true, "ios": { - "simulator": true, - "distribution": "store" + "simulator": true } }, "preview": { "autoIncrement": true, "channel": "preview", "environment": "preview", - "distribution": "internal", - "ios": { - "distribution": "store" - } + "distribution": "internal" }, "preview-simulator": { "extends": "preview", - "withoutCredentials": true, "ios": { - "simulator": true, - "distribution": "internal" + "simulator": true } }, "production": { @@ -40,7 +38,6 @@ }, "production-simulator": { "extends": "production", - "withoutCredentials": true, "distribution": "internal", "ios": { "simulator": true diff --git a/hooks/useAppNavigation.ts b/hooks/useAppNavigation.ts index ab4300ec1..658f39a23 100644 --- a/hooks/useAppNavigation.ts +++ b/hooks/useAppNavigation.ts @@ -7,7 +7,7 @@ import type { AppView, NavigationStackItem } from '@/store/localStore'; import { useLocalStore } from '@/store/localStore'; import { profiler } from '@/utils/profiler'; import { useCallback, useMemo } from 'react'; -import { useLocalization } from './useLocalization'; +import { useLocalization } from '@/hooks/useLocalization'; export interface NavigationState { view: AppView; diff --git a/hooks/useColorScheme.tsx b/hooks/useColorScheme.tsx index 0d8decaa7..50b38285f 100644 --- a/hooks/useColorScheme.tsx +++ b/hooks/useColorScheme.tsx @@ -5,22 +5,30 @@ import { } from 'nativewind'; export function getColorScheme(): 'light' | 'dark' { - const colorScheme = nativewindColorScheme.get(); const stateTheme = useLocalStore.getState().theme; - return stateTheme === 'system' ? colorScheme! : stateTheme; + if (stateTheme !== 'system') { + return stateTheme; + } + // Fallback to 'light' if nativewindColorScheme is not initialized + if (!nativewindColorScheme) { + return 'light'; + } + const colorScheme = nativewindColorScheme.get(); + return colorScheme ?? 'light'; } export function useColorScheme() { - // const colorScheme = useNativeColorScheme(); const { colorScheme } = useNativewindColorScheme(); const localTheme = useLocalStore((state) => state.theme); const setLocalTheme = useLocalStore((state) => state.setTheme); + const colorSchemeOrDefault = colorScheme ?? 'dark'; + return { stateTheme: localTheme, - colorScheme: colorScheme ?? 'dark', - isDarkColorScheme: colorScheme === 'dark', + colorScheme: colorSchemeOrDefault, + isDarkColorScheme: colorSchemeOrDefault === 'dark', setColorScheme: setLocalTheme }; } diff --git a/hooks/useLocalization.tsx b/hooks/useLocalization.tsx index 6a8e62b73..2a75efc3f 100644 --- a/hooks/useLocalization.tsx +++ b/hooks/useLocalization.tsx @@ -45,7 +45,24 @@ function mapLanguoidNameToSupportedLanguage( 'bahasa indonesia': 'indonesian', // Nepali names and endonyms nepali: 'nepali', - नेपाली: 'nepali' + नेपाली: 'nepali', + // Hindi names and endonyms + hindi: 'hindi', + हिन्दी: 'hindi', + हिंदी: 'hindi', + // Burmese names and endonyms + burmese: 'burmese', + မြန်မာ: 'burmese', + myanmar: 'burmese', + // Thai names and endonyms + thai: 'thai', + ไทย: 'thai', + // Mandarin names and endonyms + mandarin: 'mandarin', + 'mandarin chinese': 'mandarin', + 普通话: 'mandarin', + 中文: 'mandarin', + chinese: 'mandarin' }; return mapping[normalized] ?? 'english'; diff --git a/hooks/useMicrophoneEnergy.ts b/hooks/useMicrophoneEnergy.ts index e74141e60..9098cd48c 100644 --- a/hooks/useMicrophoneEnergy.ts +++ b/hooks/useMicrophoneEnergy.ts @@ -1,4 +1,7 @@ -import { Audio } from 'expo-av'; +import { + getRecordingPermissionsAsync, + requestRecordingPermissionsAsync +} from 'expo-audio'; import { useCallback, useEffect, useRef, useState } from 'react'; import type { SharedValue } from 'react-native-reanimated'; import { useSharedValue } from 'react-native-reanimated'; @@ -89,8 +92,7 @@ export function useMicrophoneEnergy(): UseMicrophoneEnergy { const requestPermissions = useCallback(async (): Promise => { try { - // Use expo-av for all permission handling - const status = await Audio.requestPermissionsAsync(); + const status = await requestRecordingPermissionsAsync(); return status.granted; } catch (error) { setState((prev) => ({ @@ -109,8 +111,7 @@ export function useMicrophoneEnergy(): UseMicrophoneEnergy { } try { - // Check permissions first using expo-av - const permission = await Audio.getPermissionsAsync(); + const permission = await getRecordingPermissionsAsync(); if (!permission.granted) { const granted = await requestPermissions(); if (!granted) throw new Error('Microphone permissions required'); diff --git a/maestro/sign-in.yaml b/maestro/sign-in.yaml deleted file mode 100644 index a05b42639..000000000 --- a/maestro/sign-in.yaml +++ /dev/null @@ -1,25 +0,0 @@ -appId: ${MAESTRO_APP_ID} ---- -- launchApp: - clearState: true -- assertVisible: Terms & Privacy -- tapOn: - point: 8%,86% -- tapOn: Accept -- assertVisible: Every language. Every culture. -- tapOn: - id: onboarding-close-button -- tapOn: - text: Sign In - index: -1 -- tapOn: Enter your email -- inputText: ${MAESTRO_TEST_EMAIL} -- tapOn: Enter your password -- inputText: ${MAESTRO_TEST_PASSWORD} -- hideKeyboard -- tapOn: Sign In -- assertVisible: My Projects -- tapOn: - point: 89%,5% -- tapOn: Profile -- assertVisible: Profile diff --git a/modules/microphone-energy/android/src/main/java/expo/modules/microphoneenergy/MicrophoneEnergyModule.kt b/modules/microphone-energy/android/src/main/java/expo/modules/microphoneenergy/MicrophoneEnergyModule.kt index 9ac4377e4..e8c56c063 100644 --- a/modules/microphone-energy/android/src/main/java/expo/modules/microphoneenergy/MicrophoneEnergyModule.kt +++ b/modules/microphone-energy/android/src/main/java/expo/modules/microphoneenergy/MicrophoneEnergyModule.kt @@ -2,11 +2,15 @@ import android.media.AudioFormat import android.media.AudioRecord +import android.media.MediaCodec +import android.media.MediaExtractor +import android.media.MediaFormat import android.media.MediaRecorder import expo.modules.kotlin.Promise import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition import kotlinx.coroutines.* +import java.nio.ByteOrder import kotlin.math.abs import kotlin.math.log10 import kotlin.math.max @@ -58,6 +62,9 @@ class MicrophoneEnergyModule : Module() { AsyncFunction("disableVAD") { promise: Promise -> disableVAD(); promise.resolve(null) } AsyncFunction("startSegment") { options: Map?, promise: Promise -> startSegment(options, promise) } AsyncFunction("stopSegment") { promise: Promise -> stopSegment(promise) } + AsyncFunction("extractWaveform") { uri: String, barCount: Int, normalize: Boolean?, promise: Promise -> + extractWaveform(uri, barCount, normalize ?: true, promise) + } } private fun startEnergyDetection(promise: Promise) { @@ -308,4 +315,258 @@ class MicrophoneEnergyModule : Module() { segmentFile = null; segmentBuffers.clear() } } + + private fun extractWaveform(uri: String, barCount: Int, normalize: Boolean, promise: Promise) { + CoroutineScope(Dispatchers.IO).launch { + try { + val amplitudes = extractWaveformNative(uri, barCount, normalize) + withContext(Dispatchers.Main) { promise.resolve(amplitudes.toList()) } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + promise.reject("WAVEFORM_ERROR", "Failed to extract waveform: ${e.message}", e) + } + } + } + } + + private fun extractWaveformNative(uri: String, barCount: Int, normalize: Boolean): DoubleArray { + val extractor = MediaExtractor() + val path = if (uri.startsWith("file://")) uri.removePrefix("file://") else uri + extractor.setDataSource(path) + + var audioTrackIndex = -1 + for (i in 0 until extractor.trackCount) { + val format = extractor.getTrackFormat(i) + val mime = format.getString(MediaFormat.KEY_MIME) ?: "" + if (mime.startsWith("audio/")) { audioTrackIndex = i; break } + } + if (audioTrackIndex < 0) throw Exception("No audio track found") + + extractor.selectTrack(audioTrackIndex) + val format = extractor.getTrackFormat(audioTrackIndex) + val mime = format.getString(MediaFormat.KEY_MIME) ?: throw Exception("Missing MIME type") + var sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE) + var channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT) + val durationUs = if (format.containsKey(MediaFormat.KEY_DURATION)) format.getLong(MediaFormat.KEY_DURATION) else -1L + var totalSamples = if (durationUs > 0) (durationUs * sampleRate / 1_000_000L) else -1L + + val sumSquares = DoubleArray(barCount) + val counts = IntArray(barCount) + val collectedSamples = if (totalSamples <= 0) ArrayList() else null + + val codec = MediaCodec.createDecoderByType(mime) + codec.configure(format, null, null, 0) + codec.start() + + val bufferInfo = MediaCodec.BufferInfo() + var inputDone = false + var outputDone = false + var sampleIndex = 0L + var pcmEncoding = AudioFormat.ENCODING_PCM_16BIT + + while (!outputDone) { + if (!inputDone) { + val inputIndex = codec.dequeueInputBuffer(10_000) + if (inputIndex >= 0) { + val inputBuffer = codec.getInputBuffer(inputIndex) + if (inputBuffer != null) { + val sampleSize = extractor.readSampleData(inputBuffer, 0) + if (sampleSize < 0) { + codec.queueInputBuffer( + inputIndex, + 0, + 0, + 0, + MediaCodec.BUFFER_FLAG_END_OF_STREAM + ) + inputDone = true + } else { + val presentationTimeUs = extractor.sampleTime + codec.queueInputBuffer(inputIndex, 0, sampleSize, presentationTimeUs, 0) + extractor.advance() + } + } + } + } + + val outputIndex = codec.dequeueOutputBuffer(bufferInfo, 10_000) + when (outputIndex) { + MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> { + val outputFormat = codec.outputFormat + if (outputFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE)) { + sampleRate = outputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) + } + if (outputFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT)) { + channelCount = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) + } + if (outputFormat.containsKey(MediaFormat.KEY_PCM_ENCODING)) { + pcmEncoding = outputFormat.getInteger(MediaFormat.KEY_PCM_ENCODING) + } + if (durationUs > 0) { + totalSamples = (durationUs * sampleRate / 1_000_000L) + } + } + MediaCodec.INFO_TRY_AGAIN_LATER -> {} + else -> { + if (outputIndex >= 0) { + if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { + outputDone = true + } + + val outputBuffer = codec.getOutputBuffer(outputIndex) + if (outputBuffer != null && bufferInfo.size > 0) { + outputBuffer.position(bufferInfo.offset) + outputBuffer.limit(bufferInfo.offset + bufferInfo.size) + outputBuffer.order(ByteOrder.LITTLE_ENDIAN) + + if (pcmEncoding == AudioFormat.ENCODING_PCM_FLOAT) { + val floatBuffer = outputBuffer.asFloatBuffer() + val samples = FloatArray(floatBuffer.remaining()) + floatBuffer.get(samples) + sampleIndex = processPcmSamples( + samples, + channelCount, + barCount, + totalSamples, + sumSquares, + counts, + collectedSamples, + sampleIndex + ) + } else { + val shortBuffer = outputBuffer.asShortBuffer() + val samples = ShortArray(shortBuffer.remaining()) + shortBuffer.get(samples) + sampleIndex = processPcmSamples( + samples, + channelCount, + barCount, + totalSamples, + sumSquares, + counts, + collectedSamples, + sampleIndex + ) + } + } + + codec.releaseOutputBuffer(outputIndex, false) + } + } + } + } + + codec.stop() + codec.release() + extractor.release() + + val amplitudes = DoubleArray(barCount) + if (collectedSamples != null) { + val totalFrames = collectedSamples.size + for (bar in 0 until barCount) { + val startFrame = (bar * totalFrames) / barCount + val endFrame = ((bar + 1) * totalFrames) / barCount + val frameCount = endFrame - startFrame + if (frameCount <= 0) continue + var sum = 0.0 + for (f in startFrame until endFrame) { + val sample = collectedSamples[f].toDouble() / 32768.0 + sum += sample * sample + } + amplitudes[bar] = kotlin.math.sqrt(sum / frameCount.toDouble()) + } + } else { + for (bar in 0 until barCount) { + val count = counts[bar] + amplitudes[bar] = if (count > 0) kotlin.math.sqrt(sumSquares[bar] / count.toDouble()) else 0.0 + } + } + + if (normalize) { + val maxAmp = amplitudes.maxOrNull() ?: 0.0 + if (maxAmp > 0) { + for (i in amplitudes.indices) amplitudes[i] = amplitudes[i] / maxAmp + } + } + + return amplitudes + } + + private fun processPcmSamples( + samples: ShortArray, + channelCount: Int, + barCount: Int, + totalSamples: Long, + sumSquares: DoubleArray, + counts: IntArray, + collectedSamples: ArrayList?, + startSampleIndex: Long + ): Long { + var sampleIndex = startSampleIndex + var i = 0 + while (i < samples.size) { + val channels = max(1, channelCount) + var sum = 0 + var ch = 0 + while (ch < channels && i + ch < samples.size) { + sum += samples[i + ch].toInt() + ch++ + } + val avgSample = (sum / channels).toShort() + val normalized = avgSample.toDouble() / 32768.0 + + if (collectedSamples != null) { + collectedSamples.add(avgSample) + } else if (totalSamples > 0) { + val bin = ((sampleIndex * barCount) / totalSamples) + .toInt() + .coerceIn(0, barCount - 1) + sumSquares[bin] += normalized * normalized + counts[bin] += 1 + } + + sampleIndex++ + i += channels + } + return sampleIndex + } + + private fun processPcmSamples( + samples: FloatArray, + channelCount: Int, + barCount: Int, + totalSamples: Long, + sumSquares: DoubleArray, + counts: IntArray, + collectedSamples: ArrayList?, + startSampleIndex: Long + ): Long { + var sampleIndex = startSampleIndex + var i = 0 + while (i < samples.size) { + val channels = max(1, channelCount) + var sum = 0.0 + var ch = 0 + while (ch < channels && i + ch < samples.size) { + sum += samples[i + ch].toDouble() + ch++ + } + val avgSample = (sum / channels).toFloat() + val normalized = avgSample.toDouble() + + if (collectedSamples != null) { + collectedSamples.add((avgSample * 32767.0f).toInt().toShort()) + } else if (totalSamples > 0) { + val bin = ((sampleIndex * barCount) / totalSamples) + .toInt() + .coerceIn(0, barCount - 1) + sumSquares[bin] += normalized * normalized + counts[bin] += 1 + } + + sampleIndex++ + i += channels + } + return sampleIndex + } } diff --git a/modules/microphone-energy/ios/MicrophoneEnergyModule.swift b/modules/microphone-energy/ios/MicrophoneEnergyModule.swift index a9079c698..8fa38fe1a 100644 --- a/modules/microphone-energy/ios/MicrophoneEnergyModule.swift +++ b/modules/microphone-energy/ios/MicrophoneEnergyModule.swift @@ -1,4 +1,4 @@ -import ExpoModulesCore +import ExpoModulesCore import AVFoundation import Foundation @@ -63,6 +63,9 @@ public class MicrophoneEnergyModule: Module { AsyncFunction("disableVAD") { () -> Void in self.disableVAD() } AsyncFunction("startSegment") { (options: [String: Any?]?) -> Void in try await self.startSegment(options: options) } AsyncFunction("stopSegment") { () -> String? in return try await self.stopSegment() } + AsyncFunction("extractWaveform") { (uri: String, barCount: Int, normalize: Bool?) async throws -> [Double] in + return try await self.extractWaveform(uri: uri, barCount: barCount, normalize: normalize ?? true) + } } private func startEnergyDetection() async throws { @@ -78,7 +81,7 @@ public class MicrophoneEnergyModule: Module { } do { - try audioSession.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP]) + try audioSession.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetoothHFP, .allowBluetoothA2DP]) try audioSession.setActive(true) audioEngine = AVAudioEngine() @@ -103,7 +106,7 @@ public class MicrophoneEnergyModule: Module { if let converter = self.audioConverter, let desired = self.desiredFormat { guard let convertedBuffer = AVAudioPCMBuffer(pcmFormat: desired, frameCapacity: buffer.frameLength) else { return } - var inputProvided = false + nonisolated(unsafe) var inputProvided = false let inputBlock: AVAudioConverterInputBlock = { _, outStatus in if !inputProvided { outStatus.pointee = .haveData; inputProvided = true; return buffer } else { outStatus.pointee = .noDataNow; return nil } @@ -415,6 +418,80 @@ public class MicrophoneEnergyModule: Module { segmentBuffers.removeAll() return uri } + + private func extractWaveform(uri: String, barCount: Int, normalize: Bool) async throws -> [Double] { + return try await withCheckedThrowingContinuation { continuation in + DispatchQueue.global(qos: .userInitiated).async { + do { + let normalizedUri = uri.hasPrefix("file://") + ? String(uri.dropFirst("file://".count)) + : uri + let fileURL = URL(fileURLWithPath: normalizedUri) + let audioFile = try AVAudioFile(forReading: fileURL) + + let processingFormat = audioFile.processingFormat + let channelCount = Int(processingFormat.channelCount) + let frameCount = AVAudioFrameCount(audioFile.length) + + if frameCount == 0 || barCount <= 0 { + continuation.resume(returning: Array(repeating: 0, count: max(0, barCount))) + return + } + + guard let floatFormat = AVAudioFormat( + commonFormat: .pcmFormatFloat32, + sampleRate: processingFormat.sampleRate, + channels: processingFormat.channelCount, + interleaved: false + ) else { + throw NSError(domain: "MicrophoneEnergy", code: 10, userInfo: [NSLocalizedDescriptionKey: "Failed to create audio format"]) + } + + guard let buffer = AVAudioPCMBuffer(pcmFormat: floatFormat, frameCapacity: frameCount) else { + throw NSError(domain: "MicrophoneEnergy", code: 11, userInfo: [NSLocalizedDescriptionKey: "Failed to allocate audio buffer"]) + } + + try audioFile.read(into: buffer) + + guard let channelData = buffer.floatChannelData else { + throw NSError(domain: "MicrophoneEnergy", code: 12, userInfo: [NSLocalizedDescriptionKey: "No audio data in file"]) + } + + let totalFrames = Int(buffer.frameLength) + var amplitudes = Array(repeating: 0.0, count: barCount) + + for bar in 0.. 0 { + for i in 0..; startSegment(options?: { prerollMs?: number }): Promise; stopSegment(): Promise; + extractWaveform( + uri: string, + barCount: number, + normalize?: boolean + ): Promise; } // This call loads the native module object from the JSI. diff --git a/package-lock.json b/package-lock.json index c09574213..9cde0802d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,33 +1,33 @@ { "name": "langquest", - "version": "2.0.13", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "langquest", - "version": "2.0.13", + "version": "2.1.0", "hasInstallScript": true, "dependencies": { "@azure/core-asynciterator-polyfill": "^1.0.2", "@blazejkustra/react-native-alert": "^1.0.0", "@expo-google-fonts/noto-sans": "^0.4.2", - "@expo/vector-icons": "^14.0.2", "@gorhom/bottom-sheet": "^5.2.8", "@hookform/resolvers": "^5.0.1", + "@journeyapps/wa-sqlite": "^1.4.1", "@legendapp/list": "^2.0.12", - "@op-engineering/op-sqlite": "^14.1.4", + "@op-engineering/op-sqlite": "^15.2.5", "@powersync/attachments": "^2.4.0", - "@powersync/drizzle-driver": "^0.6.0", - "@powersync/op-sqlite": "^0.7.11", + "@powersync/drizzle-driver": "^0.7.2", + "@powersync/op-sqlite": "^0.8.0", "@powersync/react-native": "^1.25.1", "@powersync/tanstack-react-query": "^0.1.6", "@powersync/web": "^1.27.1", "@quidone/react-native-wheel-picker": "^1.6.1", - "@react-native-async-storage/async-storage": "2.1.2", + "@react-native-async-storage/async-storage": "2.2.0", "@react-native-community/netinfo": "11.4.1", - "@react-native-community/slider": "^5.1.1", - "@react-native-documents/picker": "^10.1.5", + "@react-native-community/slider": "5.1.1", + "@react-native-documents/picker": "^12.0.1", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.3.10", @@ -47,132 +47,115 @@ "@supabase/supabase-js": "^2.53.0", "@tanstack/react-query": "^5.84.0", "@tanstack/react-query-persist-client": "^5.84.0", - "babel-plugin-react-compiler": "^19.0.0-beta-af1b7da-20250417", + "babel-plugin-react-compiler": "^1.0.0", "base64-arraybuffer": "^1.0.2", "bidirectional-map": "^1.1.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "eslint-config-expo": "~9.2.0", + "eslint-config-expo": "~55.0.0", "eslint-plugin-drizzle": "^0.2.3", - "expo": "^53.0.20", - "expo-application": "~6.1.5", - "expo-av": "~15.1.7", - "expo-build-properties": "~0.14.8", - "expo-clipboard": "^8.0.7", - "expo-constants": "~17.1.7", - "expo-dev-client": "~5.2.4", - "expo-device": "~7.1.4", - "expo-drizzle-studio-plugin": "^0.2.0", - "expo-file-system": "~18.1.11", - "expo-haptics": "^15.0.7", - "expo-image": "~2.4.1", - "expo-json-utils": "^0.14.0", - "expo-linear-gradient": "~14.1.5", - "expo-linking": "~7.1.7", - "expo-localization": "~16.1.6", - "expo-manifests": "~0.16.6", - "expo-router": "~5.1.4", - "expo-sharing": "^14.0.7", - "expo-splash-screen": "~0.30.10", - "expo-status-bar": "~2.2.3", - "expo-system-ui": "~5.0.10", - "expo-updates": "~0.28.17", + "expo": "^55.0.0-preview.10", + "expo-application": "~55.0.5", + "expo-asset": "~55.0.4", + "expo-audio": "~55.0.5", + "expo-build-properties": "~55.0.6", + "expo-clipboard": "~55.0.5", + "expo-constants": "~55.0.4", + "expo-dev-client": "~55.0.5", + "expo-device": "~55.0.6", + "expo-drizzle-studio-plugin": "^0.2.1", + "expo-file-system": "~55.0.5", + "expo-font": "~55.0.3", + "expo-haptics": "~55.0.5", + "expo-image": "~55.0.3", + "expo-linear-gradient": "~55.0.5", + "expo-linking": "~55.0.4", + "expo-localization": "~55.0.5", + "expo-manifests": "~55.0.5", + "expo-router": "~55.0.0-preview.7", + "expo-sharing": "~55.0.6", + "expo-splash-screen": "~55.0.5", + "expo-sqlite": "~55.0.5", + "expo-status-bar": "~55.0.2", + "expo-system-ui": "~55.0.5", + "expo-updates": "~55.0.7", "js-logger": "^1.6.1", - "lucide-react-native": "^0.510.0", + "lucide-react-native": "^0.563.0", "moti": "^0.30.0", "nativewind": "^4.2.1", - "posthog-js": "^1.265.1", - "posthog-react-native": "^4.3.0", - "posthog-react-native-session-replay": "^1.1.1", - "react": "19.0.0", - "react-compiler-runtime": "^19.0.0-beta-af1b7da-20250417", - "react-dom": "19.0.0", - "react-hook-form": "^7.54.2", - "react-native": "0.79.5", + "posthog-js": "^1.345.1", + "posthog-react-native": "^4.31.0", + "posthog-react-native-session-replay": "^1.2.4", + "react": "19.2.0", + "react-compiler-runtime": "^1.0.0", + "react-dom": "19.2.0", + "react-hook-form": "^7.71.1", + "react-native": "0.83.1", "react-native-audio-concat": "^0.9.0", - "react-native-element-dropdown": "2.12.2", - "react-native-gesture-handler": "~2.24.0", - "react-native-keyboard-controller": "^1.19.5", + "react-native-element-dropdown": "2.12.4", + "react-native-gesture-handler": "~2.30.0", + "react-native-keyboard-controller": "1.20.4", "react-native-onboarding": "^1.0.6", - "react-native-pager-view": "^7.0.0", - "react-native-reanimated": "~4.1.0", + "react-native-pager-view": "8.0.0", + "react-native-reanimated": "^4.2.1", "react-native-reorderable-list": "^0.18.0", - "react-native-safe-area-context": "5.4.0", - "react-native-screens": "~4.11.1", - "react-native-sortables": "^1.9.4", - "react-native-svg": "^15.11.2", - "react-native-url-polyfill": "^2.0.0", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "4.22.0", + "react-native-svg": "15.15.1", + "react-native-url-polyfill": "^3.0.0", "react-native-uuid": "^2.0.3", "react-native-web": "^0.21.0", - "react-native-worklets": "0.5.1", - "tailwind-merge": "^3.3.0", + "react-native-worklets": "0.7.2", + "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", "testflight-dev-deploy": "^0.0.8", "vaul": "^1.1.2", - "zod": "^4.1.11", - "zustand": "^5.0.3" + "zod": "^4.3.6", + "zustand": "^5.0.11" }, "devDependencies": { "@babel/core": "^7.20.0", "@babel/plugin-transform-async-generator-functions": "^7.26.8", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@eslint/compat": "^1.2.4", - "@libsql/client": "^0.14.0", + "@libsql/client": "^0.17.0", "@modelcontextprotocol/sdk": "^1.0.4", "@react-native-community/cli": "latest", - "@react-native/debugger-frontend": "^0.83.1", - "@react-native/dev-middleware": "^0.83.1", "@total-typescript/ts-reset": "^0.6.1", "@types/bidirectional-map": "^1.0.4", "@types/default-gateway": "^7.2.2", - "@types/jest": "^29.5.12", - "@types/node": "^22.18.9", - "@types/react": "~19.0.10", + "@types/jest": "29.5.14", + "@types/node": "^25.2.2", + "@types/react": "~19.2.2", "@types/react-test-renderer": "^19.0.0", "babel-plugin-inline-import": "^3.0.0", - "babel-preset-expo": "^54.0.6", + "babel-preset-expo": "~55.0.0", "cross-env": "^10.1.0", "default-gateway": "^7.2.2", - "dotenv": "^16.4.7", - "drizzle-kit": "^0.31.4", - "drizzle-orm": "^0.44.4", + "drizzle-kit": "^0.31.9", + "drizzle-orm": "^0.45.1", "eslint": "^9.38.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-compiler": "^19.1.0-rc.2", "eslint-plugin-react-hooks": "^7.0.1", "expo-atlas": "^0.4.0", - "jest": "^29.2.1", - "jest-expo": "~53.0.9", + "jest": "~29.7.0", + "jest-expo": "~55.0.6", "jiti": "^2.6.1", - "knip": "^5.64.2", + "knip": "^5.83.1", "metro-react-native-babel-transformer": "^0.77.0", "patch-package": "^8.0.1", - "prettier": "^3.6.2", - "prettier-plugin-tailwindcss": "^0.6.14", + "prettier": "^3.8.1", + "prettier-plugin-tailwindcss": "^0.7.2", "react-native-svg-transformer": "^1.5.1", - "react-test-renderer": "19.0.0", + "react-test-renderer": "19.2.0", "sharp-cli": "^5.2.0", "supabase": "^2.48.3", - "tailwindcss": "^3.4.17", - "ts-node": "^10.9.2", - "tsx": "^4.19.2", - "typescript": "~5.8.3", - "typescript-eslint": "^8.20.0" - } - }, - "node_modules/@0no-co/graphql.web": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz", - "integrity": "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==", - "license": "MIT", - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" - }, - "peerDependenciesMeta": { - "graphql": { - "optional": true - } + "tailwindcss": "^3.4.19", + "tsx": "^4.21.0", + "typescript": "~5.9.2", + "typescript-eslint": "^8.55.0" } }, "node_modules/@alloc/quick-lru": { @@ -328,9 +311,9 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", - "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "license": "MIT", "dependencies": { "@babel/parser": "^7.29.0", @@ -2569,30 +2552,6 @@ "react-native": "*" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", @@ -3722,6 +3681,12 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@expo-google-fonts/material-symbols": { + "version": "0.4.22", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/material-symbols/-/material-symbols-0.4.22.tgz", + "integrity": "sha512-Kgn/eYeb5s/pxVkRWQLgZOdaC6SpOofUVc7mtadUQc1OwEy9meDupWCvfWE7+Y8Rh9kFpQe9UxJNvmfmV4pqHA==", + "license": "MIT AND Apache-2.0" + }, "node_modules/@expo-google-fonts/noto-sans": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@expo-google-fonts/noto-sans/-/noto-sans-0.4.2.tgz", @@ -3729,32 +3694,31 @@ "license": "MIT AND OFL-1.1" }, "node_modules/@expo/cli": { - "version": "0.24.24", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.24.24.tgz", - "integrity": "sha512-XybHfF2QNPJNnHoUKHcG796iEkX5126UuTAs6MSpZuvZRRQRj/sGCLX+driCOVHbDOpcCOusMuHrhxHbtTApyg==", + "version": "55.0.7", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-55.0.7.tgz", + "integrity": "sha512-RuhoB/M0LnD5dSTkd7lkVjCR1H2lpviZTDokLWhV/HbOzlOwXehKGA5MNjse8Jc3/ZoizFrs3b9ZYmX9APCNjA==", "license": "MIT", "dependencies": { - "@0no-co/graphql.web": "^1.0.8", - "@babel/runtime": "^7.20.0", "@expo/code-signing-certificates": "^0.0.6", - "@expo/config": "~11.0.13", - "@expo/config-plugins": "~10.1.2", - "@expo/devcert": "^1.1.2", - "@expo/env": "~1.0.7", - "@expo/image-utils": "^0.7.6", - "@expo/json-file": "^9.1.5", - "@expo/metro-config": "~0.20.18", - "@expo/osascript": "^2.2.5", - "@expo/package-manager": "^1.8.6", - "@expo/plist": "^0.3.5", - "@expo/prebuild-config": "^9.0.12", - "@expo/schema-utils": "^0.1.0", + "@expo/config": "~55.0.4", + "@expo/config-plugins": "~55.0.4", + "@expo/devcert": "^1.2.1", + "@expo/env": "~2.1.0", + "@expo/image-utils": "^0.8.12", + "@expo/json-file": "^10.0.12", + "@expo/log-box": "55.0.6", + "@expo/metro": "~54.2.0", + "@expo/metro-config": "~55.0.5", + "@expo/osascript": "^2.4.2", + "@expo/package-manager": "^1.10.3", + "@expo/plist": "^0.5.2", + "@expo/prebuild-config": "^55.0.4", + "@expo/router-server": "^55.0.5", + "@expo/schema-utils": "^55.0.2", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", - "@expo/xcpretty": "^4.3.0", - "@react-native/dev-middleware": "0.79.6", - "@urql/core": "^5.0.6", - "@urql/exchange-retry": "^1.3.0", + "@expo/xcpretty": "^4.4.0", + "@react-native/dev-middleware": "0.83.1", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", @@ -3765,89 +3729,51 @@ "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", + "dnssd-advertise": "^1.1.1", "env-editor": "^0.4.1", - "freeport-async": "^2.0.0", + "expo-server": "^55.0.3", + "fetch-nodeshim": "^0.4.5", "getenv": "^2.0.0", - "glob": "^10.4.2", + "glob": "^13.0.0", "lan-network": "^0.1.6", "minimatch": "^9.0.0", + "multitars": "^0.2.3", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^3.0.1", - "pretty-bytes": "^5.6.0", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "qrcode-terminal": "0.11.0", "require-from-string": "^2.0.2", - "requireg": "^0.2.2", - "resolve": "^1.22.2", "resolve-from": "^5.0.0", - "resolve.exports": "^2.0.3", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", - "tar": "^7.4.3", "terminal-link": "^2.1.1", - "undici": "^6.18.2", "wrap-ansi": "^7.0.0", - "ws": "^8.12.1" + "ws": "^8.12.1", + "zod": "^3.25.76" }, "bin": { "expo-internal": "build/bin/cli" - } - }, - "node_modules/@expo/cli/node_modules/@react-native/debugger-frontend": { - "version": "0.79.6", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.79.6.tgz", - "integrity": "sha512-lIK/KkaH7ueM22bLO0YNaQwZbT/oeqhaghOvmZacaNVbJR1Cdh/XAqjT8FgCS+7PUnbxA8B55NYNKGZG3O2pYw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=18" - } - }, - "node_modules/@expo/cli/node_modules/@react-native/dev-middleware": { - "version": "0.79.6", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.79.6.tgz", - "integrity": "sha512-BK3GZBa9c7XSNR27EDRtxrgyyA3/mf1j3/y+mPk7Ac0Myu85YNrXnC9g3mL5Ytwo0g58TKrAIgs1fF2Q5Mn6mQ==", - "license": "MIT", - "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.79.6", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.2.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "serve-static": "^1.16.2", - "ws": "^6.2.3" }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@expo/cli/node_modules/@react-native/dev-middleware/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@expo/cli/node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" + "peerDependencies": { + "expo": "*", + "expo-router": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo-router": { + "optional": true + }, + "react-native": { + "optional": true + } } }, "node_modules/@expo/cli/node_modules/ansi-regex": { @@ -3917,21 +3843,56 @@ } }, "node_modules/@expo/cli/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.3.tgz", + "integrity": "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.2.0", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@expo/cli/node_modules/glob/node_modules/balanced-match": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz", + "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==", + "license": "MIT", + "dependencies": { + "jackspeak": "^4.2.3" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@expo/cli/node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@expo/cli/node_modules/glob/node_modules/minimatch": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", + "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3996,12 +3957,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@expo/cli/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/@expo/cli/node_modules/onetime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", @@ -4014,22 +3969,6 @@ "node": ">=4" } }, - "node_modules/@expo/cli/node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@expo/cli/node_modules/ora": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", @@ -4075,9 +4014,9 @@ } }, "node_modules/@expo/cli/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4110,6 +4049,15 @@ "node": ">=4" } }, + "node_modules/@expo/cli/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@expo/code-signing-certificates": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz", @@ -4120,97 +4068,107 @@ } }, "node_modules/@expo/config": { - "version": "11.0.13", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-11.0.13.tgz", - "integrity": "sha512-TnGb4u/zUZetpav9sx/3fWK71oCPaOjZHoVED9NaEncktAd0Eonhq5NUghiJmkUGt3gGSjRAEBXiBbbY9/B1LA==", + "version": "55.0.4", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.4.tgz", + "integrity": "sha512-DzLYn211jhUbMY3o3m682DC+J7lXTOAW4DiMQta+/klbIMRSA3EeSPpJcmciFGk5FRff1e6NY/gF+wu1Ok6kxg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "~7.10.4", - "@expo/config-plugins": "~10.1.2", - "@expo/config-types": "^53.0.5", - "@expo/json-file": "^9.1.5", + "@babel/code-frame": "^7.20.0", + "@expo/config-plugins": "~55.0.4", + "@expo/config-types": "^55.0.4", + "@expo/json-file": "^10.0.12", "deepmerge": "^4.3.1", "getenv": "^2.0.0", - "glob": "^10.4.2", + "glob": "^13.0.0", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", - "sucrase": "3.35.0" + "sucrase": "~3.35.1" } }, "node_modules/@expo/config-plugins": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-10.1.2.tgz", - "integrity": "sha512-IMYCxBOcnuFStuK0Ay+FzEIBKrwW8OVUMc65+v0+i7YFIIe8aL342l7T4F8lR4oCfhXn7d6M5QPgXvjtc/gAcw==", + "version": "55.0.4", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-55.0.4.tgz", + "integrity": "sha512-7FBviIFvjVDxH3MKLY5fl+2hz4mZ1t7jcM1HXiFNEA2oNK5jD9TJtjfd+fnPGPTFDcE+aabCDWBHQmds3v1/Tw==", "license": "MIT", "dependencies": { - "@expo/config-types": "^53.0.5", - "@expo/json-file": "~9.1.5", - "@expo/plist": "^0.3.5", + "@expo/config-types": "^55.0.4", + "@expo/json-file": "~10.0.12", + "@expo/plist": "^0.5.2", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", - "glob": "^10.4.2", + "glob": "^13.0.0", "resolve-from": "^5.0.0", "semver": "^7.5.4", - "slash": "^3.0.0", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, + "node_modules/@expo/config-plugins/node_modules/balanced-match": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz", + "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==", + "license": "MIT", + "dependencies": { + "jackspeak": "^4.2.3" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@expo/config-plugins/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/@expo/config-plugins/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.3.tgz", + "integrity": "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.2.0", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@expo/config-plugins/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", + "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@expo/config-plugins/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4220,69 +4178,71 @@ } }, "node_modules/@expo/config-types": { - "version": "53.0.5", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-53.0.5.tgz", - "integrity": "sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g==", + "version": "55.0.4", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-55.0.4.tgz", + "integrity": "sha512-bZZOqScX2WyOZT3ThpqKr7h7Dl81Qm0OUyVxmuBbSb7cOjXy5kgwFywItsPdvc9cjeXWjJf7ESUj/kNsKfjjXw==", "license": "MIT" }, - "node_modules/@expo/config/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "node_modules/@expo/config/node_modules/balanced-match": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz", + "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==", "license": "MIT", "dependencies": { - "@babel/highlight": "^7.10.4" + "jackspeak": "^4.2.3" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/@expo/config/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/@expo/config/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.3.tgz", + "integrity": "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.2.0", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@expo/config/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", + "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@expo/config/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4310,47 +4270,66 @@ "ms": "^2.1.1" } }, + "node_modules/@expo/devtools": { + "version": "55.0.2", + "resolved": "https://registry.npmjs.org/@expo/devtools/-/devtools-55.0.2.tgz", + "integrity": "sha512-4VsFn9MUriocyuhyA+ycJP3TJhUsOFHDc270l9h3LhNpXMf6wvIdGcA0QzXkZtORXmlDybWXRP2KT1k36HcQkA==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@expo/dom-webview": { + "version": "55.0.3", + "resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-55.0.3.tgz", + "integrity": "sha512-bY4/rfcZ0f43DvOtMn8/kmPlmo01tex5hRoc5hKbwBwQjqWQuQt0ACwu7akR9IHI4j0WNG48eL6cZB6dZUFrzg==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/@expo/env": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@expo/env/-/env-1.0.7.tgz", - "integrity": "sha512-qSTEnwvuYJ3umapO9XJtrb1fAqiPlmUUg78N0IZXXGwQRt+bkp0OBls+Y5Mxw/Owj8waAM0Z3huKKskRADR5ow==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.1.0.tgz", + "integrity": "sha512-vbLItCcADX2WFlfQOlAXfdDL8opBjXUnbzShSY/+3zyrkSMoxwSTgrgS8JdoGla47pVRjpKtziD5HgJgMjf8Tg==", "license": "MIT", "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", - "dotenv": "~16.4.5", - "dotenv-expand": "~11.0.6", "getenv": "^2.0.0" - } - }, - "node_modules/@expo/env/node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" }, - "funding": { - "url": "https://dotenvx.com" + "engines": { + "node": ">=20.12.0" } }, "node_modules/@expo/fingerprint": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.13.4.tgz", - "integrity": "sha512-MYfPYBTMfrrNr07DALuLhG6EaLVNVrY/PXjEzsjWdWE4ZFn0yqI0IdHNkJG7t1gePT8iztHc7qnsx+oo/rDo6w==", + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.16.3.tgz", + "integrity": "sha512-abm2rm/3Tg1gPihTyDcy7kOWnjPdtpWHSI/VpWwLaAmqqa5hFASBcr8ge58ZEWsNiozUJbfflqOp5oTQqoumAA==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", - "find-up": "^5.0.0", "getenv": "^2.0.0", - "glob": "^10.4.2", + "glob": "^13.0.0", "ignore": "^5.3.1", "minimatch": "^9.0.0", - "p-limit": "^3.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, @@ -4368,21 +4347,56 @@ } }, "node_modules/@expo/fingerprint/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.3.tgz", + "integrity": "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.2.0", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@expo/fingerprint/node_modules/glob/node_modules/balanced-match": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz", + "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==", + "license": "MIT", + "dependencies": { + "jackspeak": "^4.2.3" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@expo/fingerprint/node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@expo/fingerprint/node_modules/glob/node_modules/minimatch": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", + "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4404,9 +4418,9 @@ } }, "node_modules/@expo/fingerprint/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4416,9 +4430,9 @@ } }, "node_modules/@expo/image-utils": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.7.6.tgz", - "integrity": "sha512-GKnMqC79+mo/1AFrmAcUcGfbsXXTRqOMNS1umebuevl3aaw+ztsYEFEiuNhHZW7PQ3Xs3URNT513ZxKhznDscw==", + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.12.tgz", + "integrity": "sha512-3KguH7kyKqq7pNwLb9j6BBdD/bjmNwXZG/HPWT6GWIXbwrvAJt2JNyYTP5agWJ8jbbuys1yuCzmkX+TU6rmI7A==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -4427,15 +4441,13 @@ "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "resolve-from": "^5.0.0", - "semver": "^7.6.0", - "temp-dir": "~2.0.0", - "unique-string": "~2.0.0" + "semver": "^7.6.0" } }, "node_modules/@expo/image-utils/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4445,49 +4457,97 @@ } }, "node_modules/@expo/json-file": { - "version": "9.1.5", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-9.1.5.tgz", - "integrity": "sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA==", + "version": "10.0.12", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.12.tgz", + "integrity": "sha512-inbDycp1rMAelAofg7h/mMzIe+Owx6F7pur3XdQ3EPTy00tme+4P6FWgHKUcjN8dBSrnbRNpSyh5/shzHyVCyQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "~7.10.4", + "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, - "node_modules/@expo/json-file/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "node_modules/@expo/local-build-cache-provider": { + "version": "55.0.3", + "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-55.0.3.tgz", + "integrity": "sha512-wdpqOSpaqsY5CZ4rnC49U3jsdRaOfzbl6MlD7oSRH2slKmVoD/CiHH+9x9uuLsp65mGTp0a+FMUL6JfuvxRPIw==", "license": "MIT", "dependencies": { - "@babel/highlight": "^7.10.4" + "@expo/config": "~55.0.4", + "chalk": "^4.1.2" + } + }, + "node_modules/@expo/log-box": { + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-55.0.6.tgz", + "integrity": "sha512-t19lLsHQT2dmdBtKohibSzNlueTYDF//h7qyF7b/SMZRcofS27y/UJsoYPJfMJbo1OdQkZyI1InZYdpzwolMsA==", + "license": "MIT", + "dependencies": { + "@expo/dom-webview": "^55.0.3", + "anser": "^1.4.9", + "stacktrace-parser": "^0.1.10" + }, + "peerDependencies": { + "@expo/dom-webview": "^55.0.3", + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/@expo/metro": { + "version": "54.2.0", + "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-54.2.0.tgz", + "integrity": "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w==", + "license": "MIT", + "dependencies": { + "metro": "0.83.3", + "metro-babel-transformer": "0.83.3", + "metro-cache": "0.83.3", + "metro-cache-key": "0.83.3", + "metro-config": "0.83.3", + "metro-core": "0.83.3", + "metro-file-map": "0.83.3", + "metro-minify-terser": "0.83.3", + "metro-resolver": "0.83.3", + "metro-runtime": "0.83.3", + "metro-source-map": "0.83.3", + "metro-symbolicate": "0.83.3", + "metro-transform-plugins": "0.83.3", + "metro-transform-worker": "0.83.3" } }, "node_modules/@expo/metro-config": { - "version": "0.20.18", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.20.18.tgz", - "integrity": "sha512-qPYq3Cq61KQO1CppqtmxA1NGKpzFOmdiL7WxwLhEVnz73LPSgneW7dV/3RZwVFkjThzjA41qB4a9pxDqtpepPg==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-55.0.5.tgz", + "integrity": "sha512-U5uEtUlPUJIH7nVleO+nLVhIjlxNbm6iN/cyP/ybQ0M7lRkRWl1ADFRwpy4z5ta/cvosmBqy6tIV0u9m5X1dqA==", "license": "MIT", "dependencies": { + "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", - "@expo/config": "~11.0.13", - "@expo/env": "~1.0.7", - "@expo/json-file": "~9.1.5", + "@expo/config": "~55.0.4", + "@expo/env": "~2.1.0", + "@expo/json-file": "~10.0.12", + "@expo/metro": "~54.2.0", "@expo/spawn-async": "^1.7.2", + "browserslist": "^4.25.0", "chalk": "^4.1.0", "debug": "^4.3.2", - "dotenv": "~16.4.5", - "dotenv-expand": "~11.0.6", "getenv": "^2.0.0", - "glob": "^10.4.2", + "glob": "^13.0.0", + "hermes-parser": "^0.29.1", "jsc-safe-url": "^0.2.4", - "lightningcss": "~1.27.0", + "lightningcss": "^1.30.1", "minimatch": "^9.0.0", "postcss": "~8.4.32", "resolve-from": "^5.0.0" + }, + "peerDependencies": { + "expo": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } } }, "node_modules/@expo/metro-config/node_modules/brace-expansion": { @@ -4499,39 +4559,77 @@ "balanced-match": "^1.0.0" } }, - "node_modules/@expo/metro-config/node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", + "node_modules/@expo/metro-config/node_modules/glob": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.3.tgz", + "integrity": "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.0", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, "engines": { - "node": ">=12" + "node": "20 || >=22" }, "funding": { - "url": "https://dotenvx.com" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@expo/metro-config/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "node_modules/@expo/metro-config/node_modules/glob/node_modules/balanced-match": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz", + "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "jackspeak": "^4.2.3" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@expo/metro-config/node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@expo/metro-config/node_modules/glob/node_modules/minimatch": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", + "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@expo/metro-config/node_modules/hermes-estree": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.29.1.tgz", + "integrity": "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==", + "license": "MIT" + }, + "node_modules/@expo/metro-config/node_modules/hermes-parser": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.29.1.tgz", + "integrity": "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.29.1" + } + }, "node_modules/@expo/metro-config/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -4548,12 +4646,27 @@ } }, "node_modules/@expo/metro-runtime": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-5.0.5.tgz", - "integrity": "sha512-P8UFTi+YsmiD1BmdTdiIQITzDMcZgronsA3RTQ4QKJjHM3bas11oGzLQOnFaIZnlEV8Rrr3m1m+RHxvnpL+t/A==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-55.0.5.tgz", + "integrity": "sha512-b8WBilddI89Sc3c9dDznvmMg7PKH8Plj39Hw6cdFoSQGULLeyA7cTAUodMe2ytSOq1n1EzuO0GPHNxwsp5wprQ==", "license": "MIT", + "dependencies": { + "@expo/log-box": "55.0.6", + "anser": "^1.4.9", + "pretty-format": "^29.7.0", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0" + }, "peerDependencies": { + "expo": "*", + "react": "*", + "react-dom": "*", "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "node_modules/@expo/npm-proofread": { @@ -4578,25 +4691,24 @@ } }, "node_modules/@expo/osascript": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.3.8.tgz", - "integrity": "sha512-/TuOZvSG7Nn0I8c+FcEaoHeBO07yu6vwDgk7rZVvAXoeAK5rkA09jRyjYsZo+0tMEFaToBeywA6pj50Mb3ny9w==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.4.2.tgz", + "integrity": "sha512-/XP7PSYF2hzOZzqfjgkoWtllyeTN8dW3aM4P6YgKcmmPikKL5FdoyQhti4eh6RK5a5VrUXJTOlTNIpIHsfB5Iw==", "license": "MIT", "dependencies": { - "@expo/spawn-async": "^1.7.2", - "exec-async": "^2.2.0" + "@expo/spawn-async": "^1.7.2" }, "engines": { "node": ">=12" } }, "node_modules/@expo/package-manager": { - "version": "1.9.10", - "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.9.10.tgz", - "integrity": "sha512-axJm+NOj3jVxep49va/+L3KkF3YW/dkV+RwzqUJedZrv4LeTqOG4rhrCaCPXHTvLqCTDKu6j0Xyd28N7mnxsGA==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.10.3.tgz", + "integrity": "sha512-ZuXiK/9fCrIuLjPSe1VYmfp0Sa85kCMwd8QQpgyi5ufppYKRtLBg14QOgUqj8ZMbJTxE0xqzd0XR7kOs3vAK9A==", "license": "MIT", "dependencies": { - "@expo/json-file": "^10.0.8", + "@expo/json-file": "^10.0.12", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", @@ -4604,25 +4716,6 @@ "resolve-workspace-root": "^2.0.0" } }, - "node_modules/@expo/package-manager/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@expo/package-manager/node_modules/@expo/json-file": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.8.tgz", - "integrity": "sha512-9LOTh1PgKizD1VXfGQ88LtDH0lRwq9lsTb4aichWTWSWqy3Ugfkhfm3BhzBIkJJfQQ5iJu3m/BoRlEIjoCGcnQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "~7.10.4", - "json5": "^2.2.3" - } - }, "node_modules/@expo/package-manager/node_modules/ansi-regex": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", @@ -4805,38 +4898,41 @@ } }, "node_modules/@expo/plist": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.3.5.tgz", - "integrity": "sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.5.2.tgz", + "integrity": "sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g==", "license": "MIT", "dependencies": { "@xmldom/xmldom": "^0.8.8", - "base64-js": "^1.2.3", + "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "node_modules/@expo/prebuild-config": { - "version": "9.0.12", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-9.0.12.tgz", - "integrity": "sha512-AKH5Scf+gEMgGxZZaimrJI2wlUJlRoqzDNn7/rkhZa5gUTnO4l6slKak2YdaH+nXlOWCNfAQWa76NnpQIfmv6Q==", + "version": "55.0.4", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-55.0.4.tgz", + "integrity": "sha512-1bp/S+w7KRRjegN6u0/jn2cHwzcAP1QbnA8DxW3ts1EdTIZee1X8bmR57JzRzGLuU3pTPV+gLYdUoycTiYtPHg==", "license": "MIT", "dependencies": { - "@expo/config": "~11.0.13", - "@expo/config-plugins": "~10.1.2", - "@expo/config-types": "^53.0.5", - "@expo/image-utils": "^0.7.6", - "@expo/json-file": "^9.1.5", - "@react-native/normalize-colors": "0.79.6", + "@expo/config": "~55.0.4", + "@expo/config-plugins": "~55.0.4", + "@expo/config-types": "^55.0.4", + "@expo/image-utils": "^0.8.12", + "@expo/json-file": "^10.0.12", + "@react-native/normalize-colors": "0.83.1", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" + }, + "peerDependencies": { + "expo": "*" } }, "node_modules/@expo/prebuild-config/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4845,10 +4941,44 @@ "node": ">=10" } }, + "node_modules/@expo/router-server": { + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-55.0.5.tgz", + "integrity": "sha512-b23SgYujpeA0qHu2lZ/H1XXuvc4TaBo92VikPUZ48YtETXMlRnccPv5Rp7N2rS7Na51wOqBEv9auPihk3d8N+A==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "@expo/metro-runtime": "^55.0.4", + "expo": "*", + "expo-constants": "^55.0.3", + "expo-font": "^55.0.3", + "expo-router": "*", + "expo-server": "^55.0.3", + "react": "*", + "react-dom": "*", + "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" + }, + "peerDependenciesMeta": { + "@expo/metro-runtime": { + "optional": true + }, + "expo-router": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-server-dom-webpack": { + "optional": true + } + } + }, "node_modules/@expo/schema-utils": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-0.1.8.tgz", - "integrity": "sha512-9I6ZqvnAvKKDiO+ZF8BpQQFYWXOJvTAL5L/227RUbWG1OVZDInFifzCBiqAZ3b67NRfeAgpgvbA7rejsqhY62A==", + "version": "55.0.2", + "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-55.0.2.tgz", + "integrity": "sha512-QZ5WKbJOWkCrMq0/kfhV9ry8te/OaS34YgLVpG8u9y2gix96TlpRTbxM/YATjNcUR2s4fiQmPCOxkGtog4i37g==", "license": "MIT" }, "node_modules/@expo/sdk-runtime-versions": { @@ -4889,12 +5019,12 @@ "license": "MIT" }, "node_modules/@expo/vector-icons": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.1.0.tgz", - "integrity": "sha512-7T09UE9h8QDTsUeMGymB4i+iqvtEeaO5VvUjryFB4tugDTG/bkzViWA74hm5pfjjDEhYMXWaX112mcvhccmIwQ==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.0.3.tgz", + "integrity": "sha512-SBUyYKphmlfUBqxSfDdJ3jAdEVSALS2VUPOUyqn48oZmb2TL/O7t7/PQm5v4NQujYEPLPMTLn9KVw6H7twwbTA==", "license": "MIT", "peerDependencies": { - "expo-font": "*", + "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } @@ -5503,179 +5633,70 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "license": "BlueOak-1.0.0", "engines": { - "node": "20 || >=22" + "node": ">=18" } }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@isaacs/balanced-match": "^4.0.1" + "minipass": "^7.0.4" }, "engines": { - "node": "20 || >=22" + "node": ">=18.0.0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, "engines": { "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "dependencies": { + "sprintf-js": "~1.0.2" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { "node": ">=8" @@ -6036,8 +6057,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/@journeyapps/wa-sqlite/-/wa-sqlite-1.4.1.tgz", "integrity": "sha512-xAWys6opteBpWaKmHG1pZvBmQViEKFK/46YVEkYlWxa4F9VAG0gIjCpfIdcQvXdqZf7X3ByADGmNBcR/cJ1DqQ==", - "hasInstallScript": true, - "peer": true + "hasInstallScript": true }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", @@ -6108,23 +6128,23 @@ } }, "node_modules/@libsql/client": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.14.0.tgz", - "integrity": "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.17.0.tgz", + "integrity": "sha512-TLjSU9Otdpq0SpKHl1tD1Nc9MKhrsZbCFGot3EbCxRa8m1E5R1mMwoOjKMMM31IyF7fr+hPNHLpYfwbMKNusmg==", "devOptional": true, "license": "MIT", "dependencies": { - "@libsql/core": "^0.14.0", - "@libsql/hrana-client": "^0.7.0", + "@libsql/core": "^0.17.0", + "@libsql/hrana-client": "^0.9.0", "js-base64": "^3.7.5", - "libsql": "^0.4.4", + "libsql": "^0.5.22", "promise-limit": "^2.7.0" } }, "node_modules/@libsql/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.14.0.tgz", - "integrity": "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.17.0.tgz", + "integrity": "sha512-hnZRnJHiS+nrhHKLGYPoJbc78FE903MSDrFJTbftxo+e52X+E0Y0fHOCVYsKWcg6XgB7BbJYUrz/xEkVTSaipw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -6132,9 +6152,9 @@ } }, "node_modules/@libsql/darwin-arm64": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.7.tgz", - "integrity": "sha512-yOL742IfWUlUevnI5PdnIT4fryY3LYTdLm56bnY0wXBw7dhFcnjuA7jrH3oSVz2mjZTHujxoITgAE7V6Z+eAbg==", + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.22.tgz", + "integrity": "sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA==", "cpu": [ "arm64" ], @@ -6145,9 +6165,9 @@ ] }, "node_modules/@libsql/darwin-x64": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.7.tgz", - "integrity": "sha512-ezc7V75+eoyyH07BO9tIyJdqXXcRfZMbKcLCeF8+qWK5nP8wWuMcfOVywecsXGRbT99zc5eNra4NEx6z5PkSsA==", + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.22.tgz", + "integrity": "sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA==", "cpu": [ "x64" ], @@ -6158,28 +6178,18 @@ ] }, "node_modules/@libsql/hrana-client": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", - "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.9.0.tgz", + "integrity": "sha512-pxQ1986AuWfPX4oXzBvLwBnfgKDE5OMhAdR/5cZmRaB4Ygz5MecQybvwZupnRz341r2CtFmbk/BhSu7k2Lm+Jw==", "devOptional": true, "license": "MIT", "dependencies": { - "@libsql/isomorphic-fetch": "^0.3.1", "@libsql/isomorphic-ws": "^0.1.5", + "cross-fetch": "^4.0.0", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, - "node_modules/@libsql/isomorphic-fetch": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", - "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@libsql/isomorphic-ws": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", @@ -6191,10 +6201,36 @@ "ws": "^8.13.0" } }, + "node_modules/@libsql/linux-arm-gnueabihf": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.22.tgz", + "integrity": "sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm-musleabihf": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.22.tgz", + "integrity": "sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@libsql/linux-arm64-gnu": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.7.tgz", - "integrity": "sha512-WlX2VYB5diM4kFfNaYcyhw5y+UJAI3xcMkEUJZPtRDEIu85SsSFrQ+gvoKfcVh76B//ztSeEX2wl9yrjF7BBCA==", + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.22.tgz", + "integrity": "sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA==", "cpu": [ "arm64" ], @@ -6205,9 +6241,9 @@ ] }, "node_modules/@libsql/linux-arm64-musl": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.7.tgz", - "integrity": "sha512-6kK9xAArVRlTCpWeqnNMCoXW1pe7WITI378n4NpvU5EJ0Ok3aNTIC2nRPRjhro90QcnmLL1jPcrVwO4WD1U0xw==", + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.22.tgz", + "integrity": "sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw==", "cpu": [ "arm64" ], @@ -6218,9 +6254,9 @@ ] }, "node_modules/@libsql/linux-x64-gnu": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.7.tgz", - "integrity": "sha512-CMnNRCmlWQqqzlTw6NeaZXzLWI8bydaXDke63JTUCvu8R+fj/ENsLrVBtPDlxQ0wGsYdXGlrUCH8Qi9gJep0yQ==", + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.22.tgz", + "integrity": "sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew==", "cpu": [ "x64" ], @@ -6231,9 +6267,9 @@ ] }, "node_modules/@libsql/linux-x64-musl": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.7.tgz", - "integrity": "sha512-nI6tpS1t6WzGAt1Kx1n1HsvtBbZ+jHn0m7ogNNT6pQHZQj7AFFTIMeDQw/i/Nt5H38np1GVRNsFe99eSIMs9XA==", + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.22.tgz", + "integrity": "sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg==", "cpu": [ "x64" ], @@ -6244,9 +6280,9 @@ ] }, "node_modules/@libsql/win32-x64-msvc": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.7.tgz", - "integrity": "sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw==", + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.22.tgz", + "integrity": "sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA==", "cpu": [ "x64" ], @@ -6257,9 +6293,9 @@ ] }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.25.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.3.tgz", - "integrity": "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "dev": true, "license": "MIT", "dependencies": { @@ -6271,14 +6307,15 @@ "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "jose": "^6.1.1", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.0" + "zod-to-json-schema": "^3.25.1" }, "engines": { "node": ">=18" @@ -6436,12 +6473,13 @@ } }, "node_modules/@op-engineering/op-sqlite": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@op-engineering/op-sqlite/-/op-sqlite-14.1.4.tgz", - "integrity": "sha512-ZIZAqfHUKIjSxhaxWovEz4kCp6Gtoi8RPnJ36lPwTr73c7pEFNidE2vFm0dMBEj2ikm9wfYkab1/boW98SkVKA==", + "version": "15.2.5", + "resolved": "https://registry.npmjs.org/@op-engineering/op-sqlite/-/op-sqlite-15.2.5.tgz", + "integrity": "sha512-Vmgwt0AzY7qoge3X6EONhsb5NlM2yoQUF0/lseUWBelfc9BUili7/DFsFsS73cvtYWlwPpqeTGOoce5mzHozBw==", "license": "MIT", "workspaces": [ - "example" + "example", + "node" ], "peerDependencies": { "react": "*", @@ -6557,12 +6595,12 @@ } }, "node_modules/@opentelemetry/resources": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz", - "integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.1.tgz", + "integrity": "sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.5.0", + "@opentelemetry/core": "2.5.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -6573,9 +6611,9 @@ } }, "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", - "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.1.tgz", + "integrity": "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" @@ -6695,9 +6733,9 @@ } }, "node_modules/@oxc-resolver/binding-android-arm-eabi": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.17.0.tgz", - "integrity": "sha512-kVnY21v0GyZ/+LG6EIO48wK3mE79BUuakHUYLIqobO/Qqq4mJsjuYXMSn3JtLcKZpN1HDVit4UHpGJHef1lrlw==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.17.1.tgz", + "integrity": "sha512-+VuZyMYYaap5uDAU1xDU3Kul0FekLqpBS8kI5JozlWfYQKnc/HsZg2gHPkQrj0SC9lt74WMNCfOzZZJlYXSdEQ==", "cpu": [ "arm" ], @@ -6709,9 +6747,9 @@ ] }, "node_modules/@oxc-resolver/binding-android-arm64": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.17.0.tgz", - "integrity": "sha512-Pf8e3XcsK9a8RHInoAtEcrwf2vp7V9bSturyUUYxw9syW6E7cGi7z9+6ADXxm+8KAevVfLA7pfBg8NXTvz/HOw==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.17.1.tgz", + "integrity": "sha512-YlDDTjvOEKhom/cRSVsXsMVeXVIAM9PJ/x2mfe08rfuS0iIEfJd8PngKbEIhG72WPxleUa+vkEZj9ncmC14z3Q==", "cpu": [ "arm64" ], @@ -6723,9 +6761,9 @@ ] }, "node_modules/@oxc-resolver/binding-darwin-arm64": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.17.0.tgz", - "integrity": "sha512-lVSgKt3biecofXVr8e1hnfX0IYMd4A6VCxmvOmHsFt5Zbmt0lkO4S2ap2bvQwYDYh5ghUNamC7M2L8K6vishhQ==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.17.1.tgz", + "integrity": "sha512-HOYYLSY4JDk14YkXaz/ApgJYhgDP4KsG8EZpgpOxdszGW9HmIMMY/vXqVKYW74dSH+GQkIXYxBrEh3nv+XODVg==", "cpu": [ "arm64" ], @@ -6737,9 +6775,9 @@ ] }, "node_modules/@oxc-resolver/binding-darwin-x64": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.17.0.tgz", - "integrity": "sha512-+/raxVJE1bo7R4fA9Yp0wm3slaCOofTEeUzM01YqEGcRDLHB92WRGjRhagMG2wGlvqFuSiTp81DwSbBVo/g6AQ==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.17.1.tgz", + "integrity": "sha512-JHPJbsa5HvPq2/RIdtGlqfaG9zV2WmgvHrKTYmlW0L5esqtKCBuetFudXTBzkNcyD69kSZLzH92AzTr6vFHMFg==", "cpu": [ "x64" ], @@ -6751,9 +6789,9 @@ ] }, "node_modules/@oxc-resolver/binding-freebsd-x64": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.17.0.tgz", - "integrity": "sha512-x9Ks56n+n8h0TLhzA6sJXa2tGh3uvMGpBppg6PWf8oF0s5S/3p/J6k1vJJ9lIUtTmenfCQEGKnFokpRP4fLTLg==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.17.1.tgz", + "integrity": "sha512-UD1FRC8j8xZstFXYsXwQkNmmg7vUbee006IqxokwDUUA+xEgKZDpLhBEiVKM08Urb+bn7Q0gn6M1pyNR0ng5mg==", "cpu": [ "x64" ], @@ -6765,9 +6803,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.17.0.tgz", - "integrity": "sha512-Wf3w07Ow9kXVJrS0zmsaFHKOGhXKXE8j1tNyy+qIYDsQWQ4UQZVx5SjlDTcqBnFerlp3Z3Is0RjmVzgoLG3qkA==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.17.1.tgz", + "integrity": "sha512-wFWC1wyf2ROFWTxK5x0Enm++DSof3EBQ/ypyAesMDLiYxOOASDoMOZG1ylWUnlKaCt5W7eNOWOzABpdfFf/ssA==", "cpu": [ "arm" ], @@ -6779,9 +6817,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.17.0.tgz", - "integrity": "sha512-N0OKA1al1gQ5Gm7Fui1RWlXaHRNZlwMoBLn3TVtSXX+WbnlZoVyDqqOqFL8+pVEHhhxEA2LR8kmM0JO6FAk6dg==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.17.1.tgz", + "integrity": "sha512-k/hUif0GEBk/csSqCfTPXb8AAVs1NNWCa/skBghvNbTtORcWfOVqJ3mM+2pE189+enRm4UnryLREu5ysI0kXEQ==", "cpu": [ "arm" ], @@ -6793,9 +6831,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.17.0.tgz", - "integrity": "sha512-wdcQ7Niad9JpjZIGEeqKJnTvczVunqlZ/C06QzR5zOQNeLVRScQ9S5IesKWUAPsJQDizV+teQX53nTK+Z5Iy+g==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.17.1.tgz", + "integrity": "sha512-Cwm6A071ww60QouJ9LoHAwBgEoZzHQ0Qaqk2E7WLfBdiQN9mLXIDhnrpn04hlRElRPhLiu/dtg+o5PPLvaINXQ==", "cpu": [ "arm64" ], @@ -6807,9 +6845,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm64-musl": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.17.0.tgz", - "integrity": "sha512-65B2/t39HQN5AEhkLsC+9yBD1iRUkKOIhfmJEJ7g6wQ9kylra7JRmNmALFjbsj0VJsoSQkpM8K07kUZuNJ9Kxw==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.17.1.tgz", + "integrity": "sha512-+hwlE2v3m0r3sk93SchJL1uyaKcPjf+NGO/TD2DZUDo+chXx7FfaEj0nUMewigSt7oZ2sQN9Z4NJOtUa75HE5Q==", "cpu": [ "arm64" ], @@ -6821,9 +6859,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.17.0.tgz", - "integrity": "sha512-kExgm3TLK21dNMmcH+xiYGbc6BUWvT03PUZ2aYn8mUzGPeeORklBhg3iYcaBI3ZQHB25412X1Z6LLYNjt4aIaA==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.17.1.tgz", + "integrity": "sha512-bO+rsaE5Ox8cFyeL5Ct5tzot1TnQpFa/Wmu5k+hqBYSH2dNVDGoi0NizBN5QV8kOIC6O5MZr81UG4yW/2FyDTA==", "cpu": [ "ppc64" ], @@ -6835,9 +6873,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.17.0.tgz", - "integrity": "sha512-1utUJC714/ydykZQE8c7QhpEyM4SaslMfRXxN9G61KYazr6ndt85LaubK3EZCSD50vVEfF4PVwFysCSO7LN9uA==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.17.1.tgz", + "integrity": "sha512-B/P+hxKQ1oX4YstI9Lyh4PGzqB87Ddqj/A4iyRBbPdXTcxa+WW3oRLx1CsJKLmHPdDk461Hmbghq1Bm3pl+8Aw==", "cpu": [ "riscv64" ], @@ -6849,9 +6887,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.17.0.tgz", - "integrity": "sha512-mayiYOl3LMmtO2CLn4I5lhanfxEo0LAqlT/EQyFbu1ZN3RS+Xa7Q3JEM0wBpVIyfO/pqFrjvC5LXw/mHNDEL7A==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.17.1.tgz", + "integrity": "sha512-ulp2H3bFXzd/th2maH+QNKj5qgOhJ3v9Yspdf1svTw3CDOuuTl6sRKsWQ7MUw0vnkSNvQndtflBwVXgzZvURsQ==", "cpu": [ "riscv64" ], @@ -6863,9 +6901,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.17.0.tgz", - "integrity": "sha512-Ow/yI+CrUHxIIhn/Y1sP/xoRKbCC3x9O1giKr3G/pjMe+TCJ5ZmfqVWU61JWwh1naC8X5Xa7uyLnbzyYqPsHfg==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.17.1.tgz", + "integrity": "sha512-LAXYVe3rKk09Zo9YKF2ZLBcH8sz8Oj+JIyiUxiHtq0hiYLMsN6dOpCf2hzQEjPAmsSEA/hdC1PVKeXo+oma8mQ==", "cpu": [ "s390x" ], @@ -6877,9 +6915,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-x64-gnu": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.17.0.tgz", - "integrity": "sha512-Z4J7XlPMQOLPANyu6y3B3V417Md4LKH5bV6bhqgaG99qLHmU5LV2k9ErV14fSqoRc/GU/qOpqMdotxiJqN/YWg==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.17.1.tgz", + "integrity": "sha512-3RAhxipMKE8RCSPn7O//sj440i+cYTgYbapLeOoDvQEt6R1QcJjTsFgI4iz99FhVj3YbPxlZmcLB5VW+ipyRTA==", "cpu": [ "x64" ], @@ -6891,9 +6929,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-x64-musl": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.17.0.tgz", - "integrity": "sha512-0effK+8lhzXsgsh0Ny2ngdnTPF30v6QQzVFApJ1Ctk315YgpGkghkelvrLYYgtgeFJFrzwmOJ2nDvCrUFKsS2Q==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.17.1.tgz", + "integrity": "sha512-wpjMEubGU8r9VjZTLdZR3aPHaBqTl8Jl8F4DBbgNoZ+yhkhQD1/MGvY70v2TLnAI6kAHSvcqgfvaqKDa2iWsPQ==", "cpu": [ "x64" ], @@ -6905,9 +6943,9 @@ ] }, "node_modules/@oxc-resolver/binding-openharmony-arm64": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.17.0.tgz", - "integrity": "sha512-kFB48dRUW6RovAICZaxHKdtZe+e94fSTNA2OedXokzMctoU54NPZcv0vUX5PMqyikLIKJBIlW7laQidnAzNrDA==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.17.1.tgz", + "integrity": "sha512-XIE4w17RYAVIgx+9Gs3deTREq5tsmalbatYOOBGNdH7n0DfTE600c7wYXsp7ANc3BPDXsInnOzXDEPCvO1F6cg==", "cpu": [ "arm64" ], @@ -6919,9 +6957,9 @@ ] }, "node_modules/@oxc-resolver/binding-wasm32-wasi": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.17.0.tgz", - "integrity": "sha512-a3elKSBLPT0OoRPxTkCIIc+4xnOELolEBkPyvdj01a6PSdSmyJ1NExWjWLaXnT6wBMblvKde5RmSwEi3j+jZpg==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.17.1.tgz", + "integrity": "sha512-Lqi5BlHX3zS4bpSOkIbOKVf7DIk6Gvmdifr2OuOI58eUUyP944M8/OyaB09cNpPy9Vukj7nmmhOzj8pwLgAkIg==", "cpu": [ "wasm32" ], @@ -6936,9 +6974,9 @@ } }, "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.17.0.tgz", - "integrity": "sha512-4eszUsSDb9YVx0RtYkPWkxxtSZIOgfeiX//nG5cwRRArg178w4RCqEF1kbKPud9HPrp1rXh7gE4x911OhvTnPg==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.17.1.tgz", + "integrity": "sha512-l6lTcLBQVj1HNquFpXSsrkCIM8X5Hlng5YNQJrg00z/KyovvDV5l3OFhoRyZ+aLBQ74zUnMRaJZC7xcBnHyeNg==", "cpu": [ "arm64" ], @@ -6950,9 +6988,9 @@ ] }, "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.17.0.tgz", - "integrity": "sha512-t946xTXMmR7yGH0KAe9rB055/X4EPIu93JUvjchl2cizR5QbuwkUV7vLS2BS6x6sfvDoQb6rWYnV1HCci6tBSg==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.17.1.tgz", + "integrity": "sha512-VTzVtfnCCsU/6GgvursWoyZrhe3Gj/RyXzDWmh4/U1Y3IW0u1FZbp+hCIlBL16pRPbDc5YvXVtCOnA41QOrOoQ==", "cpu": [ "ia32" ], @@ -6964,9 +7002,9 @@ ] }, "node_modules/@oxc-resolver/binding-win32-x64-msvc": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.17.0.tgz", - "integrity": "sha512-pX6s2kMXLQg+hlqKk5UqOW09iLLxnTkvn8ohpYp2Mhsm2yzDPCx9dyOHiB/CQixLzTkLQgWWJykN4Z3UfRKW4Q==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.17.1.tgz", + "integrity": "sha512-jRPVU+6/12baj87q2+UGRh30FBVBzqKdJ7rP/mSqiL1kpNQB9yZ1j0+m3sru1m+C8hiFK7lBFwjUtYUBI7+UpQ==", "cpu": [ "x64" ], @@ -6977,16 +7015,6 @@ "win32" ] }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", @@ -7000,18 +7028,18 @@ } }, "node_modules/@posthog/core": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.19.0.tgz", - "integrity": "sha512-OMcdu5cJcvkle2hw0rpe+1mTOFRlerTHTtZKZFvB8z0hgzbN1WeaGZfGFY5wOq42LVTSxwdUgK1MYERyzG1Epw==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.22.0.tgz", + "integrity": "sha512-WkmOnq95aAOu6yk6r5LWr5cfXsQdpVbWDCwOxQwxSne8YV6GuZET1ziO5toSQXgrgbdcjrSz2/GopAfiL6iiAA==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6" } }, "node_modules/@posthog/types": { - "version": "1.338.0", - "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.338.0.tgz", - "integrity": "sha512-OruyKCjjovPUnag0p9CEQ0QeSOKhsd1ztDOcr6mISBeKG7bE+Dg8pR1sBHZKiCdkEurB1RcufvSQyNBTddWLMA==", + "version": "1.347.2", + "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.347.2.tgz", + "integrity": "sha512-aT+r/7jXOzPmUHO6sutoWzczPcYIZyhmWt1f1OvY4zKC7Pwp/ZsJWKFTxjV02p0PZz96AE83eLTe7w7b6tjhIw==", "license": "MIT" }, "node_modules/@powersync/attachments": { @@ -7034,41 +7062,34 @@ } }, "node_modules/@powersync/drizzle-driver": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@powersync/drizzle-driver/-/drizzle-driver-0.6.0.tgz", - "integrity": "sha512-O1izduCdV6V9QO3ibcDyGkbvbc1fuUmQkh6UxYXClKdIdx+8+cOeWy2O97IR2Iu55XWWI6nZB4MjbZIUGHgeAw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@powersync/drizzle-driver/-/drizzle-driver-0.7.2.tgz", + "integrity": "sha512-urzkvimN44qWufmNonDdvwI3tcN9qFA0XB6BpoQDXFonkRzjunnZiUHas558PdDiw98AEJ4bD+FbtvvgJKzDcw==", "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.1" + }, "peerDependencies": { - "@powersync/common": "^1.38.0", + "@powersync/common": "^1.46.0", "drizzle-orm": "<1.0.0" } }, "node_modules/@powersync/op-sqlite": { - "version": "0.7.18", - "resolved": "https://registry.npmjs.org/@powersync/op-sqlite/-/op-sqlite-0.7.18.tgz", - "integrity": "sha512-25d+Wm/qeB6o4gButieNm372frPwLCHzmgp3jf4CSWmm+PMeu2FPTFsv+x2jsF4aVPy7fkTItCRXlvqZS3t2fQ==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@powersync/op-sqlite/-/op-sqlite-0.8.0.tgz", + "integrity": "sha512-1FNzuSipyGCtxROkhg2DG7mOZjOtPMTj+YEgHqSVFuMdrkzjx31jmf+Lw5VI+UeCWNSUNuUcD/UMrrEUAmRxBw==", "license": "Apache-2.0", "dependencies": { - "@powersync/common": "1.45.0", - "async-lock": "^1.4.0" + "@powersync/common": "1.46.0", + "async-lock": "^1.4.1" }, "peerDependencies": { - "@op-engineering/op-sqlite": "^13.0.0 || ^14.0.0", - "@powersync/common": "^1.45.0", + "@op-engineering/op-sqlite": "^13.0.0 || ^14.0.0 || ^15.0.0", + "@powersync/common": "^1.46.0", "react": "*", "react-native": "*" } }, - "node_modules/@powersync/op-sqlite/node_modules/@powersync/common": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/@powersync/common/-/common-1.45.0.tgz", - "integrity": "sha512-C0r0Lago5MPcGiXSQhX6CDEqmHuMLjHZJzHjk61un1cvn+7SDoJrX1KTln+HuvoLovBTKK/DUJoAGzpTCIFF5Q==", - "license": "Apache-2.0", - "dependencies": { - "async-mutex": "^0.5.0", - "event-iterator": "^2.0.0" - } - }, "node_modules/@powersync/react": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/@powersync/react/-/react-1.8.2.tgz", @@ -8159,9 +8180,9 @@ "license": "MIT" }, "node_modules/@react-native-async-storage/async-storage": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz", - "integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", "license": "MIT", "dependencies": { "merge-options": "^3.0.4" @@ -8279,9 +8300,9 @@ } }, "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "devOptional": true, "license": "ISC", "bin": { @@ -8379,9 +8400,9 @@ } }, "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "devOptional": true, "license": "ISC", "bin": { @@ -8412,9 +8433,9 @@ } }, "node_modules/@react-native-community/cli/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "devOptional": true, "license": "ISC", "bin": { @@ -8434,49 +8455,50 @@ } }, "node_modules/@react-native-community/slider": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-5.1.2.tgz", - "integrity": "sha512-UV/MjCyCtSjS5BQDrrGIMmCXm309xEG6XbR0Dj65kzTraJSVDxSjQS2uBUXgX+5SZUOCzCxzv3OufOZBdtQY4w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-5.1.1.tgz", + "integrity": "sha512-W98If/LnTaziU3/0h5+G1LvJaRhMc6iLQBte6UWa4WBIHDMaDPglNBIFKcCXc9Dxp83W+f+5Wv22Olq9M2HJYA==", "license": "MIT" }, "node_modules/@react-native-documents/picker": { - "version": "10.1.7", - "resolved": "https://registry.npmjs.org/@react-native-documents/picker/-/picker-10.1.7.tgz", - "integrity": "sha512-Tb8SPU+pHxrSJmDHBozSUStIPeyFHTHLrU3MW0N3sUAioLd5z+nmUdypfg5fs+Yzp7KTxVW06APe2HLB1ysLww==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@react-native-documents/picker/-/picker-12.0.1.tgz", + "integrity": "sha512-vpJKb4t/5bnxe9+gQl+plJfKrrIsmYwANGhNH2B9E1dS1+6FDBzg4Dwmcq4ueaGfkRKEPJ606mJttVEH1ZKZaA==", "license": "MIT", + "funding": { + "url": "https://github.com/react-native-documents/document-picker?sponsor=1" + }, "peerDependencies": { "react": "*", - "react-native": "*" + "react-native": ">=0.79.0" } }, "node_modules/@react-native/assets-registry": { - "version": "0.79.5", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.5.tgz", - "integrity": "sha512-N4Kt1cKxO5zgM/BLiyzuuDNquZPiIgfktEQ6TqJ/4nKA8zr4e8KJgU6Tb2eleihDO4E24HmkvGc73naybKRz/w==", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.1.tgz", + "integrity": "sha512-AT7/T6UwQqO39bt/4UL5EXvidmrddXrt0yJa7ENXndAv+8yBzMsZn6fyiax6+ERMt9GLzAECikv3lj22cn2wJA==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 20.19.4" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.81.5", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.81.5.tgz", - "integrity": "sha512-oF71cIH6je3fSLi6VPjjC3Sgyyn57JLHXs+mHWc9MoCiJJcM4nqsS5J38zv1XQ8d3zOW2JtHro+LF0tagj2bfQ==", - "dev": true, + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.1.tgz", + "integrity": "sha512-VPj8O3pG1ESjZho9WVKxqiuryrotAECPHGF5mx46zLUYNTWR5u9OMUXYk7LeLy+JLWdGEZ2Gn3KoXeFZbuqE+g==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.81.5" + "@react-native/codegen": "0.83.1" }, "engines": { "node": ">= 20.19.4" } }, "node_modules/@react-native/babel-preset": { - "version": "0.81.5", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.81.5.tgz", - "integrity": "sha512-UoI/x/5tCmi+pZ3c1+Ypr1DaRMDLI3y+Q70pVLLVgrnC3DHsHRIbHcCHIeG/IJvoeFqFM2sTdhSOLJrf8lOPrA==", - "dev": true, + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.1.tgz", + "integrity": "sha512-xI+tbsD4fXcI6PVU4sauRCh0a5fuLQC849SINmU2J5wP8kzKu4Ye0YkGjUW3mfGrjaZcjkWmF6s33jpyd3gdTw==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -8520,8 +8542,8 @@ "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.81.5", - "babel-plugin-syntax-hermes-parser": "0.29.1", + "@react-native/babel-plugin-codegen": "0.83.1", + "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, @@ -8532,27 +8554,25 @@ "@babel/core": "*" } }, - "node_modules/@react-native/babel-preset/node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "dev": true, + "node_modules/@react-native/babel-preset/node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", + "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "hermes-parser": "0.32.0" } }, "node_modules/@react-native/codegen": { - "version": "0.81.5", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.81.5.tgz", - "integrity": "sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g==", - "dev": true, + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.1.tgz", + "integrity": "sha512-FpRxenonwH+c2a5X5DZMKUD7sCudHxB3eSQPgV9R+uxd28QWslyAWrpnJM/Az96AEksHnymDzEmzq2HLX5nb+g==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", - "hermes-parser": "0.29.1", + "hermes-parser": "0.32.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" @@ -8565,98 +8585,39 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.79.5", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.79.5.tgz", - "integrity": "sha512-ApLO1ARS8JnQglqS3JAHk0jrvB+zNW3dvNJyXPZPoygBpZVbf8sjvqeBiaEYpn8ETbFWddebC4HoQelDndnrrA==", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.1.tgz", + "integrity": "sha512-FqR1ftydr08PYlRbrDF06eRiiiGOK/hNmz5husv19sK6iN5nHj1SMaCIVjkH/a5vryxEddyFhU6PzO/uf4kOHg==", "license": "MIT", "dependencies": { - "@react-native/dev-middleware": "0.79.5", - "chalk": "^4.0.0", - "debug": "^2.2.0", + "@react-native/dev-middleware": "0.83.1", + "debug": "^4.4.0", "invariant": "^2.2.4", - "metro": "^0.82.0", - "metro-config": "^0.82.0", - "metro-core": "^0.82.0", + "metro": "^0.83.3", + "metro-config": "^0.83.3", + "metro-core": "^0.83.3", "semver": "^7.1.3" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { - "@react-native-community/cli": "*" + "@react-native-community/cli": "*", + "@react-native/metro-config": "*" }, "peerDependenciesMeta": { "@react-native-community/cli": { "optional": true + }, + "@react-native/metro-config": { + "optional": true } } }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/debugger-frontend": { - "version": "0.79.5", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.79.5.tgz", - "integrity": "sha512-WQ49TRpCwhgUYo5/n+6GGykXmnumpOkl4Lr2l2o2buWU9qPOwoiBqJAtmWEXsAug4ciw3eLiVfthn5ufs0VB0A==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/dev-middleware": { - "version": "0.79.5", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.79.5.tgz", - "integrity": "sha512-U7r9M/SEktOCP/0uS6jXMHmYjj4ESfYCkNAenBjFjjsRWekiHE+U/vRMeO+fG9gq4UCcBAUISClkQCowlftYBw==", - "license": "MIT", - "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.79.5", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.2.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "serve-static": "^1.16.2", - "ws": "^6.2.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/@react-native/community-cli-plugin/node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@react-native/community-cli-plugin/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -8665,20 +8626,10 @@ "node": ">=10" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, "node_modules/@react-native/debugger-frontend": { "version": "0.83.1", "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.1.tgz", "integrity": "sha512-01Rn3goubFvPjHXONooLmsW0FLxJDKIUJNOlOS0cPtmmTIx9YIjxhe/DxwHXGk7OnULd7yl3aYy7WlBsEd5Xmg==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 20.19.4" @@ -8688,7 +8639,6 @@ "version": "0.83.1", "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.1.tgz", "integrity": "sha512-d+0w446Hxth5OP/cBHSSxOEpbj13p2zToUy6e5e3tTERNJ8ueGlW7iGwGTrSymNDgXXFjErX+dY4P4/3WokPIQ==", - "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6", @@ -8702,7 +8652,6 @@ "version": "0.83.1", "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.1.tgz", "integrity": "sha512-QJaSfNRzj3Lp7MmlCRgSBlt1XZ38xaBNXypXAp/3H3OdFifnTZOeYOpFmcpjcXYnDqkxetuwZg8VL65SQhB8dg==", - "dev": true, "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", @@ -8726,7 +8675,6 @@ "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0", @@ -8743,7 +8691,6 @@ "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.3.0" @@ -8762,43 +8709,43 @@ } }, "node_modules/@react-native/gradle-plugin": { - "version": "0.79.5", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.79.5.tgz", - "integrity": "sha512-K3QhfFNKiWKF3HsCZCEoWwJPSMcPJQaeqOmzFP4RL8L3nkpgUwn74PfSCcKHxooVpS6bMvJFQOz7ggUZtNVT+A==", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.83.1.tgz", + "integrity": "sha512-6ESDnwevp1CdvvxHNgXluil5OkqbjkJAkVy7SlpFsMGmVhrSxNAgD09SSRxMNdKsnLtzIvMsFCzyHLsU/S4PtQ==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 20.19.4" } }, "node_modules/@react-native/js-polyfills": { - "version": "0.79.5", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.79.5.tgz", - "integrity": "sha512-a2wsFlIhvd9ZqCD5KPRsbCQmbZi6KxhRN++jrqG0FUTEV5vY7MvjjUqDILwJd2ZBZsf7uiDuClCcKqA+EEdbvw==", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.1.tgz", + "integrity": "sha512-qgPpdWn/c5laA+3WoJ6Fak8uOm7CG50nBsLlPsF8kbT7rUHIVB9WaP6+GPsoKV/H15koW7jKuLRoNVT7c3Ht3w==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 20.19.4" } }, "node_modules/@react-native/normalize-colors": { - "version": "0.79.6", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.79.6.tgz", - "integrity": "sha512-0v2/ruY7eeKun4BeKu+GcfO+SHBdl0LJn4ZFzTzjHdWES0Cn+ONqKljYaIv8p9MV2Hx/kcdEvbY4lWI34jC/mQ==", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.1.tgz", + "integrity": "sha512-84feABbmeWo1kg81726UOlMKAhcQyFXYz2SjRKYkS78QmfhVDhJ2o/ps1VjhFfBz0i/scDwT1XNv9GwmRIghkg==", "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { - "version": "0.79.5", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.5.tgz", - "integrity": "sha512-EUPM2rfGNO4cbI3olAbhPkIt3q7MapwCwAJBzUfWlZ/pu0PRNOnMQ1IvaXTf3TpeozXV52K1OdprLEI/kI5eUA==", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.83.1.tgz", + "integrity": "sha512-MdmoAbQUTOdicCocm5XAFDJWsswxk7hxa6ALnm6Y88p01HFML0W593hAn6qOt9q6IM1KbAcebtH6oOd4gcQy8w==", "license": "MIT", "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { - "@types/react": "^19.0.0", + "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, @@ -8809,9 +8756,9 @@ } }, "node_modules/@react-navigation/bottom-tabs": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.12.0.tgz", - "integrity": "sha512-/GtOfVWRligHG0mvX39I1FGdUWeWl0GVF2okEziQSQj0bOTrLIt7y44C3r/aCLkEpTVltCPGM3swqGTH3UfRCw==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.13.0.tgz", + "integrity": "sha512-qxxjRDpjhZ4vIZqG4rBU1Vx2jgOAO/ciUKc9sJqVlTM005E2E+aK1EaE3lGaBDyZxTpjonollAucZcqL7OTscQ==", "license": "MIT", "dependencies": { "@react-navigation/elements": "^2.9.5", @@ -9303,9 +9250,9 @@ "license": "BSD-3-Clause" }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "license": "MIT" }, "node_modules/@sinonjs/commons": { @@ -9333,9 +9280,9 @@ "license": "MIT" }, "node_modules/@supabase/auth-js": { - "version": "2.94.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.94.0.tgz", - "integrity": "sha512-FPFx8DzEreSoLo2HVfwNY0p/uNQ9rONQp3VKw68UP8wg1YwXK5g+TM4d4U7LTGW4HqwG0rjUdQ1it7QPw09r2w==", + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.95.3.tgz", + "integrity": "sha512-vD2YoS8E2iKIX0F7EwXTmqhUpaNsmbU6X2R0/NdFcs02oEfnHyNP/3M716f3wVJ2E5XHGiTFXki6lRckhJ0Thg==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -9345,9 +9292,9 @@ } }, "node_modules/@supabase/functions-js": { - "version": "2.94.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.94.0.tgz", - "integrity": "sha512-DAbIptT7e7hAvYHp4FhRH+LxxvKQ38QGxjaFHLoDoeQBqDaAbP/iu74dLOn6PIAnSRAqUkN2bKGs3awzNzBgKA==", + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.95.3.tgz", + "integrity": "sha512-uTuOAKzs9R/IovW1krO0ZbUHSJnsnyJElTXIRhjJTqymIVGcHzkAYnBCJqd7468Fs/Foz1BQ7Dv6DCl05lr7ig==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -9357,9 +9304,9 @@ } }, "node_modules/@supabase/postgrest-js": { - "version": "2.94.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.94.0.tgz", - "integrity": "sha512-3YKoDJu8VxvlJdCe2U2edzSQ9uArR0OLM3A4eAsS4QnIqzs+HuY5ZnubeoWnn/zRNeTENMLSXXZHAeMBPkyXew==", + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.95.3.tgz", + "integrity": "sha512-LTrRBqU1gOovxRm1vRXPItSMPBmEFqrfTqdPTRtzOILV4jPSueFz6pES5hpb4LRlkFwCPRmv3nQJ5N625V2Xrg==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -9369,9 +9316,9 @@ } }, "node_modules/@supabase/realtime-js": { - "version": "2.94.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.94.0.tgz", - "integrity": "sha512-TTPVttf4yMZTd0Jo65rIn4eyTAlI7XlwgB6OVEnne4Sz4VOddXPavEw4xRISOKJZ1n8ULLRz03hilMtqnj9gNg==", + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.95.3.tgz", + "integrity": "sha512-D7EAtfU3w6BEUxDACjowWNJo/ZRo7sDIuhuOGKHIm9FHieGeoJV5R6GKTLtga/5l/6fDr2u+WcW/m8I9SYmaIw==", "license": "MIT", "dependencies": { "@types/phoenix": "^1.6.6", @@ -9384,9 +9331,9 @@ } }, "node_modules/@supabase/storage-js": { - "version": "2.94.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.94.0.tgz", - "integrity": "sha512-wLdfqKqSfdDgGbLqgsT8ssEELBaHJm1xwiymq3cvVgxcbjRR6ECtUGtA1kj0JvX/F9DiARbrk/zkIsQ+OaUVBg==", + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.95.3.tgz", + "integrity": "sha512-4GxkJiXI3HHWjxpC3sDx1BVrV87O0hfX+wvJdqGv67KeCu+g44SPnII8y0LL/Wr677jB7tpjAxKdtVWf+xhc9A==", "license": "MIT", "dependencies": { "iceberg-js": "^0.8.1", @@ -9397,16 +9344,16 @@ } }, "node_modules/@supabase/supabase-js": { - "version": "2.94.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.94.0.tgz", - "integrity": "sha512-KcqoA3ITW++CwoyCFxwV10npzR6wMfjKbMz87Q1PSuLw26SmHFQjjbBLvuZpzOrPoQ67on5W55irFsK8e0BhWg==", + "version": "2.95.3", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.95.3.tgz", + "integrity": "sha512-Fukw1cUTQ6xdLiHDJhKKPu6svEPaCEDvThqCne3OaQyZvuq2qjhJAd91kJu3PXLG18aooCgYBaB6qQz35hhABg==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.94.0", - "@supabase/functions-js": "2.94.0", - "@supabase/postgrest-js": "2.94.0", - "@supabase/realtime-js": "2.94.0", - "@supabase/storage-js": "2.94.0" + "@supabase/auth-js": "2.95.3", + "@supabase/functions-js": "2.95.3", + "@supabase/postgrest-js": "2.95.3", + "@supabase/realtime-js": "2.95.3", + "@supabase/storage-js": "2.95.3" }, "engines": { "node": ">=20.0.0" @@ -9763,9 +9710,9 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.20", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", - "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", + "version": "5.90.21", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", + "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", "license": "MIT", "dependencies": { "@tanstack/query-core": "5.90.20" @@ -9850,34 +9797,6 @@ "node": ">=10.13.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "devOptional": true, - "license": "MIT" - }, "node_modules/@tsconfig/node18": { "version": "18.2.6", "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.6.tgz", @@ -10028,12 +9947,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz", - "integrity": "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA==", + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/phoenix": { @@ -10043,12 +9962,12 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.0.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.14.tgz", - "integrity": "sha512-ixLZ7zG7j1fM0DijL9hDArwhwcCb4vqmePgwtV0GfnkHRSCUEv4LvzarcTdhoqgyMznUx/EhoTUv31CKZzkQlw==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", "dependencies": { - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { @@ -10119,16 +10038,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", - "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/type-utils": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -10141,7 +10060,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", + "@typescript-eslint/parser": "^8.55.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -10156,15 +10075,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", - "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3" }, "engines": { @@ -10180,13 +10099,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", + "@typescript-eslint/tsconfig-utils": "^8.55.0", + "@typescript-eslint/types": "^8.55.0", "debug": "^4.4.3" }, "engines": { @@ -10201,13 +10120,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10218,9 +10137,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10234,14 +10153,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", - "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -10258,9 +10177,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10271,15 +10190,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/project-service": "8.55.0", + "@typescript-eslint/tsconfig-utils": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -10322,9 +10241,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -10334,15 +10253,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10357,12 +10276,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -10373,6 +10292,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", @@ -10635,29 +10560,6 @@ "win32" ] }, - "node_modules/@urql/core": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.2.0.tgz", - "integrity": "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==", - "license": "MIT", - "dependencies": { - "@0no-co/graphql.web": "^1.0.13", - "wonka": "^6.3.2" - } - }, - "node_modules/@urql/exchange-retry": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.3.2.tgz", - "integrity": "sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg==", - "license": "MIT", - "dependencies": { - "@urql/core": "^5.1.2", - "wonka": "^6.3.2" - }, - "peerDependencies": { - "@urql/core": "^5.0.0" - } - }, "node_modules/@vscode/sudo-prompt": { "version": "9.3.2", "resolved": "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz", @@ -11192,6 +11094,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "devOptional": true, "license": "MIT" }, "node_modules/async-lock": { @@ -11234,8 +11137,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", @@ -11373,9 +11275,9 @@ } }, "node_modules/babel-plugin-react-compiler": { - "version": "19.0.0-beta-ebf51a3-20250411", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.0.0-beta-ebf51a3-20250411.tgz", - "integrity": "sha512-q84bNR9JG1crykAlJUt5Ud0/5BUyMFuQww/mrwIQDFBaxsikqBDj3f/FNDsVd2iR26A1HvXKWPEIfgJDv8/V2g==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", + "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "license": "MIT", "dependencies": { "@babel/types": "^7.26.0" @@ -11385,19 +11287,32 @@ "version": "0.21.2", "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz", "integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==", - "dev": true, "license": "MIT" }, "node_modules/babel-plugin-syntax-hermes-parser": { "version": "0.29.1", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.29.1.tgz", "integrity": "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA==", - "dev": true, "license": "MIT", "dependencies": { "hermes-parser": "0.29.1" } }, + "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-estree": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.29.1.tgz", + "integrity": "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==", + "license": "MIT" + }, + "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-parser": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.29.1.tgz", + "integrity": "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.29.1" + } + }, "node_modules/babel-plugin-syntax-trailing-function-commas": { "version": "7.0.0-beta.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", @@ -11441,12 +11356,12 @@ } }, "node_modules/babel-preset-expo": { - "version": "54.0.10", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-54.0.10.tgz", - "integrity": "sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw==", - "dev": true, + "version": "55.0.4", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.4.tgz", + "integrity": "sha512-GGPhL1bpJOFOQFRTkkVyhitf1CRtNjdVq2F1Ps7CWfw0fK/AmzAGO79cnnHuD24GhnwQe7C+Eip5U6ALbvPE3w==", "license": "MIT", "dependencies": { + "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", @@ -11462,7 +11377,7 @@ "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-preset": "0.81.5", + "@react-native/babel-preset": "0.83.1", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.29.1", @@ -11473,6 +11388,7 @@ "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", + "expo-widgets": "^55.0.0-alpha.6", "react-refresh": ">=0.14.0 <1.0.0" }, "peerDependenciesMeta": { @@ -11481,19 +11397,12 @@ }, "expo": { "optional": true + }, + "expo-widgets": { + "optional": true } } }, - "node_modules/babel-preset-expo/node_modules/babel-plugin-react-compiler": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", - "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.26.0" - } - }, "node_modules/babel-preset-fbjs": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", @@ -12005,39 +11914,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "license": "MIT", - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-callsite/node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "license": "MIT", - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -12066,9 +11942,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001767", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz", - "integrity": "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "funding": [ { "type": "opencollective", @@ -12150,6 +12026,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -12661,13 +12538,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true, - "license": "MIT" - }, "node_modules/cross-env": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", @@ -12687,9 +12557,10 @@ } }, "node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "devOptional": true, "license": "MIT", "dependencies": { "node-fetch": "^2.7.0" @@ -12699,6 +12570,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "devOptional": true, "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -12719,18 +12591,21 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "devOptional": true, "license": "MIT" }, "node_modules/cross-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "devOptional": true, "license": "BSD-2-Clause" }, "node_modules/cross-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "devOptional": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", @@ -12751,15 +12626,6 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/css-in-js-utils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", @@ -13036,15 +12902,6 @@ } } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -13311,16 +13168,6 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, - "node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "devOptional": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -13348,6 +13195,12 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dnssd-advertise": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.3.tgz", + "integrity": "sha512-XENsHi3MBzWOCAXif3yZvU1Ah0l+nhJj1sjWL6TnOAYKvGiFhbTx32xHN7+wLMLUOCj7Nr0evADWG4R8JtqCDA==", + "license": "MIT" + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -13460,37 +13313,10 @@ "tslib": "^2.0.3" } }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dotenv-expand": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", - "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", - "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/drizzle-kit": { - "version": "0.31.8", - "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.8.tgz", - "integrity": "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==", + "version": "0.31.9", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.9.tgz", + "integrity": "sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==", "dev": true, "license": "MIT", "dependencies": { @@ -13504,9 +13330,9 @@ } }, "node_modules/drizzle-orm": { - "version": "0.44.7", - "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.7.tgz", - "integrity": "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.1.tgz", + "integrity": "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==", "license": "Apache-2.0", "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", @@ -13642,12 +13468,6 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -14099,15 +13919,15 @@ } }, "node_modules/eslint-config-expo": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-expo/-/eslint-config-expo-9.2.0.tgz", - "integrity": "sha512-TQgmSx+2mRM7qUS0hB5kTDrHcSC35rA1UzOSgK5YRLmSkSMlKLmXkUrhwOpnyo9D/nHdf4ERRAySRYxgA6dlrw==", + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-expo/-/eslint-config-expo-55.0.0.tgz", + "integrity": "sha512-YvhaKrp1g7pR/qjdI12E5nw9y0DJZWgYr815vyW8wskGLsFvxATY3mtKL8zm3ZYzWj3Bvc37tRIS661TEkrv9A==", "license": "MIT", "dependencies": { "@typescript-eslint/eslint-plugin": "^8.18.2", "@typescript-eslint/parser": "^8.18.2", "eslint-import-resolver-typescript": "^3.6.3", - "eslint-plugin-expo": "^0.1.4", + "eslint-plugin-expo": "^1.0.0", "eslint-plugin-import": "^2.30.0", "eslint-plugin-react": "^7.37.3", "eslint-plugin-react-hooks": "^5.1.0", @@ -14213,16 +14033,17 @@ } } }, - "node_modules/eslint-config-universe/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "node_modules/eslint-config-universe/node_modules/@typescript-eslint/parser": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "license": "BSD-2-Clause", "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -14240,19 +14061,14 @@ } } }, - "node_modules/eslint-config-universe/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "node_modules/eslint-config-universe/node_modules/@typescript-eslint/scope-manager": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -14260,22 +14076,18 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/eslint-config-universe/node_modules/@typescript-eslint/parser": { + "node_modules/eslint-config-universe/node_modules/@typescript-eslint/type-utils": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", - "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -14293,23 +14105,6 @@ } } }, - "node_modules/eslint-config-universe/node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/eslint-config-universe/node_modules/@typescript-eslint/types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", @@ -14351,6 +14146,31 @@ } } }, + "node_modules/eslint-config-universe/node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, "node_modules/eslint-config-universe/node_modules/@typescript-eslint/visitor-keys": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", @@ -14417,9 +14237,9 @@ } }, "node_modules/eslint-config-universe/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -14549,9 +14369,9 @@ } }, "node_modules/eslint-plugin-expo": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-expo/-/eslint-plugin-expo-0.1.4.tgz", - "integrity": "sha512-YA7yiMacQbLJySuyJA0Eb5V65obqp6fVOWtw1JdYDRWC5MeToPrnNvhGDpk01Bv3Vm4ownuzUfvi89MXi1d6cg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-expo/-/eslint-plugin-expo-1.0.0.tgz", + "integrity": "sha512-qLtunR+cNFtC+jwYCBia5c/PJurMjSLMOV78KrEOyQK02ohZapU4dCFFnS2hfrJuw0zxfsjVkjqg3QBqi933QA==", "license": "MIT", "dependencies": { "@typescript-eslint/types": "^8.29.1", @@ -14737,19 +14557,6 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/eslint-plugin-react-compiler/node_modules/zod-validation-error": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.4.tgz", - "integrity": "sha512-+hEiRIiPobgyuFlEojnqjJnhFvg4r/i3cqgcm67eehZf/WBaK3g6cD02YU9mtdVxZjv8CzCA9n/Rhrs3yAAvAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.24.4" - } - }, "node_modules/eslint-plugin-react-hooks": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", @@ -14997,12 +14804,6 @@ "node": ">=18.0.0" } }, - "node_modules/exec-async": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz", - "integrity": "sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==", - "license": "MIT" - }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -15051,28 +14852,34 @@ } }, "node_modules/expo": { - "version": "53.0.26", - "resolved": "https://registry.npmjs.org/expo/-/expo-53.0.26.tgz", - "integrity": "sha512-zap+YY+w1kjwy9ucmtqfeyaL9qhHIpxCBnsTkiLsN8SFyR6TjPV0p8V4Tp/exg3pnc3CkjcMS8Lp/tSg8xeSJA==", + "version": "55.0.0-preview.10", + "resolved": "https://registry.npmjs.org/expo/-/expo-55.0.0-preview.10.tgz", + "integrity": "sha512-BtHgdcu/XS4yYSJTB4HHbYogT2qHl6S4usFqUPhoFfVu/F440VU3J98uUqbDLh5I8OcsZ2PbhjRpOy7uR+MExg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "0.24.24", - "@expo/config": "~11.0.13", - "@expo/config-plugins": "~10.1.2", - "@expo/fingerprint": "0.13.4", - "@expo/metro-config": "0.20.18", - "@expo/vector-icons": "^14.0.0", - "babel-preset-expo": "~13.2.5", - "expo-asset": "~11.1.7", - "expo-constants": "~17.1.8", - "expo-file-system": "~18.1.11", - "expo-font": "~13.3.2", - "expo-keep-awake": "~14.1.4", - "expo-modules-autolinking": "2.1.14", - "expo-modules-core": "2.5.0", - "react-native-edge-to-edge": "1.6.0", - "whatwg-url-without-unicode": "8.0.0-3" + "@expo/cli": "55.0.7", + "@expo/config": "~55.0.4", + "@expo/config-plugins": "~55.0.4", + "@expo/devtools": "55.0.2", + "@expo/fingerprint": "0.16.3", + "@expo/local-build-cache-provider": "55.0.3", + "@expo/log-box": "55.0.6", + "@expo/metro": "~54.2.0", + "@expo/metro-config": "55.0.5", + "@expo/vector-icons": "^15.0.2", + "@ungap/structured-clone": "^1.3.0", + "babel-preset-expo": "~55.0.4", + "expo-asset": "~55.0.4", + "expo-constants": "~55.0.4", + "expo-file-system": "~55.0.5", + "expo-font": "~55.0.3", + "expo-keep-awake": "~55.0.2", + "expo-modules-autolinking": "55.0.3", + "expo-modules-core": "55.0.8", + "pretty-format": "^29.7.0", + "react-refresh": "^0.14.2", + "whatwg-url-minimum": "^0.1.1" }, "bin": { "expo": "bin/cli", @@ -15099,22 +14906,22 @@ } }, "node_modules/expo-application": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-6.1.5.tgz", - "integrity": "sha512-ToImFmzw8luY043pWFJhh2ZMm4IwxXoHXxNoGdlhD4Ym6+CCmkAvCglg0FK8dMLzAb+/XabmOE7Rbm8KZb6NZg==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-55.0.5.tgz", + "integrity": "sha512-+TSHZMobYEe96CcKRYaNMdO9CcuKpO/hCjBcB0SeoOkEwDbfPyY3KcdoVW6fU7xyPZYN0wucCnLgTXKqAeB5sQ==", "license": "MIT", "peerDependencies": { "expo": "*" } }, "node_modules/expo-asset": { - "version": "11.1.7", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-11.1.7.tgz", - "integrity": "sha512-b5P8GpjUh08fRCf6m5XPVAh7ra42cQrHBIMgH2UXP+xsj4Wufl6pLy6jRF5w6U7DranUMbsXm8TOyq4EHy7ADg==", + "version": "55.0.4", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-55.0.4.tgz", + "integrity": "sha512-eaPPe9Sw4V0nL/KkEvuWpyeeSoGhP2fu1ZA7wkldqywhMVhhY+7kerMkQ7nPgJVtevIfkQRw7wD8ghZEzrKzmg==", "license": "MIT", "dependencies": { - "@expo/image-utils": "^0.7.6", - "expo-constants": "~17.1.7" + "@expo/image-utils": "^0.8.12", + "expo-constants": "~55.0.4" }, "peerDependencies": { "expo": "*", @@ -15297,62 +15104,36 @@ "dev": true, "license": "MIT" }, - "node_modules/expo-av": { - "version": "15.1.7", - "resolved": "https://registry.npmjs.org/expo-av/-/expo-av-15.1.7.tgz", - "integrity": "sha512-NC+JR+65sxXfQN1mOHp3QBaXTL2J+BzNwVO27XgUEc5s9NaoBTdHWElYXrfxvik6xwytZ+a7abrqfNNgsbQzsA==", + "node_modules/expo-audio": { + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-audio/-/expo-audio-55.0.5.tgz", + "integrity": "sha512-B2UpahrjHDSi4BPP9rwZIDm1aNBzZ+KwVj09nIoQo2XC5ng2DuHBUFZF5cISX7gmUjS2HhsXtV5/CvKJLUt+6g==", "license": "MIT", "peerDependencies": { "expo": "*", + "expo-asset": "*", "react": "*", - "react-native": "*", - "react-native-web": "*" - }, - "peerDependenciesMeta": { - "react-native-web": { - "optional": true - } + "react-native": "*" } }, "node_modules/expo-build-properties": { - "version": "0.14.8", - "resolved": "https://registry.npmjs.org/expo-build-properties/-/expo-build-properties-0.14.8.tgz", - "integrity": "sha512-GTFNZc5HaCS9RmCi6HspCe2+isleuOWt2jh7UEKHTDQ9tdvzkIoWc7U6bQO9lH3Mefk4/BcCUZD/utl7b1wdqw==", + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/expo-build-properties/-/expo-build-properties-55.0.6.tgz", + "integrity": "sha512-DfNRMAOGK3a2yao5RmCgNIz4bvCBFS8uHBHwYbd6RZos0n3Qqs96/l+Y9XMWJ/j7xVlXrTacw2wwwstqBGbwYA==", "license": "MIT", "dependencies": { - "ajv": "^8.11.0", + "@expo/schema-utils": "^55.0.2", + "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, - "node_modules/expo-build-properties/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/expo-build-properties/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, "node_modules/expo-build-properties/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -15362,9 +15143,9 @@ } }, "node_modules/expo-clipboard": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-8.0.8.tgz", - "integrity": "sha512-VKoBkHIpZZDJTB0jRO4/PZskHdMNOEz3P/41tmM6fDuODMpqhvyWK053X0ebspkxiawJX9lX33JXHBCvVsTTOA==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-55.0.5.tgz", + "integrity": "sha512-uzGQikfr46+E8bL//tzo+049VYFUzcc3vFgIqImjPrwF5oboKVkOvzsnwhTIQNC++fyU5i3jkLcGd+YK5EwcmA==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -15373,13 +15154,13 @@ } }, "node_modules/expo-constants": { - "version": "17.1.8", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.8.tgz", - "integrity": "sha512-sOCeMN/BWLA7hBP6lMwoEQzFNgTopk6YY03sBAmwT216IHyL54TjNseg8CRU1IQQ/+qinJ2fYWCl7blx2TiNcA==", + "version": "55.0.4", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-55.0.4.tgz", + "integrity": "sha512-zdPxsYnL8LrW91sXivdBIH5nouTDUG0npMZxaYhvEO/2p60iN0W4jK3n2sRFrPl31WctLWSs4ato/wIKnRuUFA==", "license": "MIT", "dependencies": { - "@expo/config": "~11.0.13", - "@expo/env": "~1.0.7" + "@expo/config": "~55.0.4", + "@expo/env": "~2.1.0" }, "peerDependencies": { "expo": "*", @@ -15387,83 +15168,60 @@ } }, "node_modules/expo-dev-client": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.2.4.tgz", - "integrity": "sha512-s/N/nK5LPo0QZJpV4aPijxyrzV4O49S3dN8D2fljqrX2WwFZzWwFO6dX1elPbTmddxumdcpczsdUPY+Ms8g43g==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-55.0.5.tgz", + "integrity": "sha512-p0xkyrCYXztUrVl6fqIrhUVR30Cxp5PbP9Vv1IXl2po8OkFDQklu5y908NBN/oeWpgv1lSgKpRTdZ1G2nsJ7IQ==", "license": "MIT", "dependencies": { - "expo-dev-launcher": "5.1.16", - "expo-dev-menu": "6.1.14", - "expo-dev-menu-interface": "1.10.0", - "expo-manifests": "~0.16.6", - "expo-updates-interface": "~1.1.0" + "expo-dev-launcher": "55.0.6", + "expo-dev-menu": "55.0.5", + "expo-dev-menu-interface": "55.0.1", + "expo-manifests": "~55.0.5", + "expo-updates-interface": "~55.1.1" }, "peerDependencies": { "expo": "*" } }, "node_modules/expo-dev-launcher": { - "version": "5.1.16", - "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-5.1.16.tgz", - "integrity": "sha512-tbCske9pvbozaEblyxoyo/97D6od9Ma4yAuyUnXtRET1CKAPKYS+c4fiZ+I3B4qtpZwN3JNFUjG3oateN0y6Hg==", + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-55.0.6.tgz", + "integrity": "sha512-Zrmdc0cgvizmX9Wd+Kc5iz0T5QQ2DSsx1Sedww9Ra4qQlUcL2GzoVnDqqw63zI5ax+rnMbBVWyiQUqHN1w/l/A==", "license": "MIT", "dependencies": { - "ajv": "8.11.0", - "expo-dev-menu": "6.1.14", - "expo-manifests": "~0.16.6", - "resolve-from": "^5.0.0" + "@expo/schema-utils": "^55.0.2", + "expo-dev-menu": "55.0.5", + "expo-manifests": "~55.0.5" }, "peerDependencies": { "expo": "*" } }, - "node_modules/expo-dev-launcher/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/expo-dev-launcher/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, "node_modules/expo-dev-menu": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-6.1.14.tgz", - "integrity": "sha512-yonNMg2GHJZtuisVowdl1iQjZfYP85r1D1IO+ar9D9zlrBPBJhq2XEju52jd1rDmDkmDuEhBSbPNhzIcsBNiPg==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-55.0.5.tgz", + "integrity": "sha512-NCNvsUl+GaVD/FgzEiHtNBLoj4LCE2xE40FKsYsV25csNAp2JizeR5eSIAKW2NX4UTWvgdqOQMIoXhERt7pZBw==", "license": "MIT", "dependencies": { - "expo-dev-menu-interface": "1.10.0" + "expo-dev-menu-interface": "55.0.1" }, "peerDependencies": { "expo": "*" } }, "node_modules/expo-dev-menu-interface": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-1.10.0.tgz", - "integrity": "sha512-NxtM/qot5Rh2cY333iOE87dDg1S8CibW+Wu4WdLua3UMjy81pXYzAGCZGNOeY7k9GpNFqDPNDXWyBSlk9r2pBg==", + "version": "55.0.1", + "resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-55.0.1.tgz", + "integrity": "sha512-FkNtwq1q6NmYoy28pj+ZLuHmirJgc039pQbJ167MZJIaprLcMN1yy67qA7xBHK+FNJ8AN8kGCtMTPByg5UC72A==", "license": "MIT", "peerDependencies": { "expo": "*" } }, "node_modules/expo-device": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/expo-device/-/expo-device-7.1.4.tgz", - "integrity": "sha512-HS04IiE1Fy0FRjBLurr9e5A6yj3kbmQB+2jCZvbSGpsjBnCLdSk/LCii4f5VFhPIBWJLyYuN5QqJyEAw6BcS4Q==", + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/expo-device/-/expo-device-55.0.6.tgz", + "integrity": "sha512-GCO3shHTn8zk5FUBywra3qyFXv08uNQpJSGeNg/gBBJw+GVpgtZ/9mhB3gVh3daueCV+PWRFWn14QT3lW6PwqQ==", "license": "MIT", "dependencies": { "ua-parser-js": "^0.7.33" @@ -15483,15 +15241,15 @@ } }, "node_modules/expo-eas-client": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-0.14.4.tgz", - "integrity": "sha512-TSL1BbBFIuXchJmPgbPnB7cGpOOuSGJcQ/L7gij/+zPjExwvKm5ckA5dlSulwoFhH8zQt4vb7bfISPSAWQVWBw==", + "version": "55.0.2", + "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-55.0.2.tgz", + "integrity": "sha512-fjOgSXaZFBK2Xmzn/uw0DTF3BsYv97JEa4PYXXqVCEvNJPwJB1cV1eX6Xyq6iKGIhMPH9k62sOc+oUdt094WCw==", "license": "MIT" }, "node_modules/expo-file-system": { - "version": "18.1.11", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-18.1.11.tgz", - "integrity": "sha512-HJw/m0nVOKeqeRjPjGdvm+zBi5/NxcdPf8M8P3G2JFvH5Z8vBWqVDic2O58jnT1OFEy0XXzoH9UqFu7cHg9DTQ==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-55.0.5.tgz", + "integrity": "sha512-ZD9uJS4tmmyPBHcW7j90+CUsdvfavXdSHSQWG22g7Zm1x02E5elHKOAhsv9gFDGsMDc1nvgR+IvrWlokjkXVSQ==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -15499,11 +15257,10 @@ } }, "node_modules/expo-font": { - "version": "14.0.11", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", - "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", + "version": "55.0.3", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-55.0.3.tgz", + "integrity": "sha512-DSyh0gzbVii5+Nb/0pAP3bL+CrB9u1N3YeKTx4wXQT8KUnuOlI4A0sEHIO25MfFpjjtovDn0WGzAtimbPkvJmg==", "license": "MIT", - "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -15513,20 +15270,34 @@ "react-native": "*" } }, + "node_modules/expo-glass-effect": { + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-glass-effect/-/expo-glass-effect-55.0.5.tgz", + "integrity": "sha512-ui3yOuntcqLukx32mPLcnYucRGN0zWk67euC45t6WlM6IPjXsRo6Z830drjKE7LPWbpWlRbOkdM6BpoAj+yDCQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-haptics": { - "version": "15.0.8", - "resolved": "https://registry.npmjs.org/expo-haptics/-/expo-haptics-15.0.8.tgz", - "integrity": "sha512-lftutojy8Qs8zaDzzjwM3gKHFZ8bOOEZDCkmh2Ddpe95Ra6kt2izeOfOfKuP/QEh0MZ1j9TfqippyHdRd1ZM9g==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-haptics/-/expo-haptics-55.0.5.tgz", + "integrity": "sha512-8bUIjxLtET5290p0WKyMQ8S3lvxMFEOdbA3GHt07QhgPf0eqeCtsH8/+g+ojtVT9RieZM9dZBHiqofdKpwCQfg==", "license": "MIT", "peerDependencies": { "expo": "*" } }, "node_modules/expo-image": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-2.4.1.tgz", - "integrity": "sha512-yHp0Cy4ylOYyLR21CcH6i70DeRyLRPc0yAIPFPn4BT/BpkJNaX5QMXDppcHa58t4WI3Bb8QRJRLuAQaeCtDF8A==", + "version": "55.0.3", + "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-55.0.3.tgz", + "integrity": "sha512-u/Mz/5lrzx+Qoo5yklJkPgMSXTEwB5oorhutucE8KSVBaKZMTdU7Tlmiw+hkiA2FSIADT4h2wA2wn6H43vdHxA==", "license": "MIT", + "dependencies": { + "sf-symbols-typescript": "^2.2.0" + }, "peerDependencies": { "expo": "*", "react": "*", @@ -15540,15 +15311,15 @@ } }, "node_modules/expo-json-utils": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.14.0.tgz", - "integrity": "sha512-xjGfK9dL0B1wLnOqNkX0jM9p48Y0I5xEPzHude28LY67UmamUyAACkqhZGaPClyPNfdzczk7Ej6WaRMT3HfXvw==", + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-55.0.0.tgz", + "integrity": "sha512-aupt/o5PDAb8dXDCb0JcRdkqnTLxe/F+La7jrnyd/sXlYFfRgBJLFOa1SqVFXm1E/Xam1SE/yw6eAb+DGY7Arg==", "license": "MIT" }, "node_modules/expo-keep-awake": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-14.1.4.tgz", - "integrity": "sha512-wU9qOnosy4+U4z/o4h8W9PjPvcFMfZXrlUoKTMBW7F4pLqhkkP/5G4EviPZixv4XWFMjn1ExQ5rV6BX8GwJsWA==", + "version": "55.0.2", + "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-55.0.2.tgz", + "integrity": "sha512-yxmmu0toMsszQN9ro8XuljicumudT4QwXiF7kswlDg5/48Y2HPfopod+8mOrIf7+UDUv99u6yH8NgSfWf0u+IA==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -15556,9 +15327,9 @@ } }, "node_modules/expo-linear-gradient": { - "version": "14.1.5", - "resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-14.1.5.tgz", - "integrity": "sha512-BSN3MkSGLZoHMduEnAgfhoj3xqcDWaoICgIr4cIYEx1GcHfKMhzA/O4mpZJ/WC27BP1rnAqoKfbclk1eA70ndQ==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-55.0.5.tgz", + "integrity": "sha512-zesU+u1/chAFCq+hYtV1xy/5XeKbDWTytOrvJlI3cfxUXV3Ezm9hP3xV6iCzbc72a5ROJYyXLAvMuoM4Pl9ZOg==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -15567,12 +15338,12 @@ } }, "node_modules/expo-linking": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-7.1.7.tgz", - "integrity": "sha512-ZJaH1RIch2G/M3hx2QJdlrKbYFUTOjVVW4g39hfxrE5bPX9xhZUYXqxqQtzMNl1ylAevw9JkgEfWbBWddbZ3UA==", + "version": "55.0.4", + "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-55.0.4.tgz", + "integrity": "sha512-n4gKPzxskShfkiXZoMMOcgyJ/JgwmyOCLSBxBBg3dsBetg+KGTB5vZw7oLCu/MsWuKfbsbc/eyuvzzDyWTrpJw==", "license": "MIT", "dependencies": { - "expo-constants": "~17.1.7", + "expo-constants": "~55.0.4", "invariant": "^2.2.4" }, "peerDependencies": { @@ -15581,9 +15352,9 @@ } }, "node_modules/expo-localization": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/expo-localization/-/expo-localization-16.1.6.tgz", - "integrity": "sha512-v4HwNzs8QvyKHwl40MvETNEKr77v1o9/eVC8WCBY++DIlBAvonHyJe2R9CfqpZbC4Tlpl7XV+07nLXc8O5PQsA==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-localization/-/expo-localization-55.0.5.tgz", + "integrity": "sha512-1AQvwtTqIH1aBZUiGI7kKFs5bTrfJOqYu4wCwmbB18P99dUZ8R4AfYzwmqI3aT/v1LggH5P7WjoW/NI+f40jpg==", "license": "MIT", "dependencies": { "rtl-detect": "^1.0.2" @@ -15594,24 +15365,18 @@ } }, "node_modules/expo-manifests": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-0.16.6.tgz", - "integrity": "sha512-1A+do6/mLUWF9xd3uCrlXr9QFDbjbfqAYmUy8UDLOjof1lMrOhyeC4Yi6WexA/A8dhZEpIxSMCKfn7G4aHAh4w==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-55.0.5.tgz", + "integrity": "sha512-GXSVnEkAJ1o71L/WshV/NmDda/vnD4poDDKi/oSYSMd8him2u0GfqqzCsgec6Odlqz5fMqXO4JgoBaWKtzn8Aw==", "license": "MIT", "dependencies": { - "@expo/config": "~11.0.12", - "expo-json-utils": "~0.15.0" + "@expo/config": "~55.0.4", + "expo-json-utils": "~55.0.0" }, "peerDependencies": { "expo": "*" } }, - "node_modules/expo-manifests/node_modules/expo-json-utils": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.15.0.tgz", - "integrity": "sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ==", - "license": "MIT" - }, "node_modules/expo-module-scripts": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/expo-module-scripts/-/expo-module-scripts-3.5.4.tgz", @@ -16082,15 +15847,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/expo-module-scripts/node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/expo-module-scripts/node_modules/react-test-renderer": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", @@ -16121,9 +15877,9 @@ } }, "node_modules/expo-module-scripts/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -16235,16 +15991,14 @@ } }, "node_modules/expo-modules-autolinking": { - "version": "2.1.14", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.14.tgz", - "integrity": "sha512-nT5ERXwc+0ZT/pozDoJjYZyUQu5RnXMk9jDGm5lg+PiKvsrCTSA/2/eftJGMxLkTjVI2MXp5WjSz3JRjbA7UXA==", + "version": "55.0.3", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-55.0.3.tgz", + "integrity": "sha512-C4Yc/D8BvQeZ2D30qOKVbMPAE5DzrVtdfYiSg44mCGZIKUNX1KELcAOxSOtzGbW5mFr+V0Og54TxM5TlT5Fr1g==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0", - "find-up": "^5.0.0", - "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0" }, @@ -16252,15 +16006,6 @@ "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, - "node_modules/expo-modules-autolinking/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/expo-modules-autolinking/node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -16270,109 +16015,94 @@ "node": ">= 10" } }, - "node_modules/expo-modules-autolinking/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/expo-modules-autolinking/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/expo-modules-core": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-2.5.0.tgz", - "integrity": "sha512-aIbQxZE2vdCKsolQUl6Q9Farlf8tjh/ROR4hfN1qT7QBGPl1XrJGnaOKkcgYaGrlzCPg/7IBe0Np67GzKMZKKQ==", + "version": "55.0.8", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-55.0.8.tgz", + "integrity": "sha512-WWcbsDmxqz7YQjr0dDRCpiwrWYoz/T4mxUh7ZbUzj8SzsUGrvmvczPj2kAS7hHviimRyfZcTenAh22Wuw/abBA==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" } }, "node_modules/expo-router": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.1.11.tgz", - "integrity": "sha512-6YQGqQM2rviVSiU6++hrJDPMByHZ7Oiux4XmgoSaGdaHku5QOn9911f2puEUZh2H9ALKBipw5v3ZkrECBd6Zbw==", + "version": "55.0.0-preview.7", + "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-55.0.0-preview.7.tgz", + "integrity": "sha512-nrigtPHg5/5ZIeY0Qjo6/wM7N27bTbq22FuNrTgGsNOt5V8sPkvs8lsMl6OsnvEZCKJ2xFYPkL3UGSEvGVxT9A==", "license": "MIT", "dependencies": { - "@expo/metro-runtime": "5.0.5", - "@expo/schema-utils": "^0.1.0", - "@expo/server": "^0.6.3", + "@expo/metro-runtime": "^55.0.5", + "@expo/schema-utils": "^55.0.2", "@radix-ui/react-slot": "1.2.0", - "@react-navigation/bottom-tabs": "^7.3.10", - "@react-navigation/native": "^7.1.6", - "@react-navigation/native-stack": "^7.3.10", + "@radix-ui/react-tabs": "^1.1.12", + "@react-navigation/bottom-tabs": "7.10.1", + "@react-navigation/native": "7.1.28", + "@react-navigation/native-stack": "7.10.1", "client-only": "^0.0.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "expo-glass-effect": "^55.0.5", + "expo-image": "^55.0.3", + "expo-server": "^55.0.3", + "expo-symbols": "^55.0.3", + "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", + "nanoid": "^3.3.8", + "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.1.6", "semver": "~7.6.3", "server-only": "^0.0.1", - "shallowequal": "^1.1.0" + "sf-symbols-typescript": "^2.1.0", + "shallowequal": "^1.1.0", + "use-latest-callback": "^0.2.1", + "vaul": "^1.1.2" }, "peerDependencies": { - "@react-navigation/drawer": "^7.3.9", + "@expo/log-box": "55.0.6", + "@expo/metro-runtime": "^55.0.5", + "@react-navigation/drawer": "^7.7.2", + "@testing-library/react-native": ">= 12.0.0", "expo": "*", - "expo-constants": "*", - "expo-linking": "*", + "expo-constants": "^55.0.4", + "expo-linking": "^55.0.4", + "react": "*", + "react-dom": "*", + "react-native": "*", + "react-native-gesture-handler": "*", "react-native-reanimated": "*", - "react-native-safe-area-context": "*", + "react-native-safe-area-context": ">= 5.4.0", "react-native-screens": "*", + "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "peerDependenciesMeta": { "@react-navigation/drawer": { "optional": true }, - "@testing-library/jest-native": { + "@testing-library/react-native": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native-gesture-handler": { "optional": true }, "react-native-reanimated": { "optional": true }, + "react-native-web": { + "optional": true + }, "react-server-dom-webpack": { "optional": true } } }, - "node_modules/expo-router/node_modules/@expo/server": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@expo/server/-/server-0.6.3.tgz", - "integrity": "sha512-Ea7NJn9Xk1fe4YeJ86rObHSv/bm3u/6WiQPXEqXJ2GrfYpVab2Swoh9/PnSM3KjR64JAgKjArDn1HiPjITCfHA==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "debug": "^4.3.4", - "source-map-support": "~0.5.21", - "undici": "^6.18.2 || ^7.0.0" - } - }, "node_modules/expo-router/node_modules/@radix-ui/react-slot": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", @@ -16391,6 +16121,43 @@ } } }, + "node_modules/expo-router/node_modules/@react-navigation/bottom-tabs": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.10.1.tgz", + "integrity": "sha512-MirOzKEe/rRwPSE9HMrS4niIo0LyUhewlvd01TpzQ1ipuXjH2wJbzAM9gS/r62zriB6HMHz2OY6oIRduwQJtTw==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.9.5", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0" + }, + "peerDependencies": { + "@react-navigation/native": "^7.1.28", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/expo-router/node_modules/@react-navigation/native-stack": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.10.1.tgz", + "integrity": "sha512-8jt7olKysn07HuKKSjT/ahZZTV+WaZa96o9RI7gAwh7ATlUDY02rIRttwvCyjovhSjD9KCiuJ+Hd4kwLidHwJw==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.9.5", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0", + "warn-once": "^0.1.1" + }, + "peerDependencies": { + "@react-navigation/native": "^7.1.28", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, "node_modules/expo-router/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -16403,33 +16170,48 @@ "node": ">=10" } }, + "node_modules/expo-server": { + "version": "55.0.3", + "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-55.0.3.tgz", + "integrity": "sha512-DeFRWvLb7pcxqrvDFK95Eujh/VFddRfmTGbcTcPVMhYl6om7eKYe8CgpfqIi2mK0rOnLHw7DPymmcPXXWuFbFw==", + "license": "MIT", + "engines": { + "node": ">=20.16.0" + } + }, "node_modules/expo-sharing": { - "version": "14.0.8", - "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-14.0.8.tgz", - "integrity": "sha512-A1pPr2iBrxypFDCWVAESk532HK+db7MFXbvO2sCV9ienaFXAk7lIBm6bkqgE6vzRd9O3RGdEGzYx80cYlc089Q==", + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-55.0.6.tgz", + "integrity": "sha512-4X88AnVZTWUjCEYNvHFtd2UMEX43G2zFJBxVANI8RzAjDny4QrzTgdCEFNg0ufZyGMxZL3V5gzUkXVP5y7IEYg==", "license": "MIT", + "dependencies": { + "@expo/config-plugins": "^55.0.4", + "@expo/config-types": "^55.0.4", + "@expo/plist": "^0.5.2" + }, "peerDependencies": { - "expo": "*" + "expo": "*", + "react": "*", + "react-native": "*" } }, "node_modules/expo-splash-screen": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.30.10.tgz", - "integrity": "sha512-Tt9va/sLENQDQYeOQ6cdLdGvTZ644KR3YG9aRlnpcs2/beYjOX1LHT510EGzVN9ljUTg+1ebEo5GGt2arYtPjw==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-55.0.5.tgz", + "integrity": "sha512-Q8PSVonkFEMUykDMBw9kdwNOtc9cMD7Vlz+MU4PYBTHrVQB5EDONnecmDwFDPYF8wc57ef73wkYxuVmrQO533A==", "license": "MIT", "dependencies": { - "@expo/prebuild-config": "^9.0.10" + "@expo/prebuild-config": "^55.0.4" }, "peerDependencies": { "expo": "*" } }, "node_modules/expo-sqlite": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-16.0.10.tgz", - "integrity": "sha512-tUOKxE9TpfneRG3eOfbNfhN9236SJ7IiUnP8gCqU7umd9DtgDGB/5PhYVVfl+U7KskgolgNoB9v9OZ9iwXN8Eg==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-55.0.5.tgz", + "integrity": "sha512-tYaktd5dQc3fEsmRSSvPBW80o6sq3mzLyOpQ9smMFWSGKvcSJsfvpbE6c8axgdhRMb3OMF4UAhASui+kL9GONQ==", "license": "MIT", - "peer": true, "dependencies": { "await-lock": "^2.2.2" }, @@ -16440,13 +16222,12 @@ } }, "node_modules/expo-status-bar": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-2.2.3.tgz", - "integrity": "sha512-+c8R3AESBoduunxTJ8353SqKAKpxL6DvcD8VKBuh81zzJyUUbfB4CVjr1GufSJEKsMzNPXZU+HJwXx7Xh7lx8Q==", + "version": "55.0.2", + "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-55.0.2.tgz", + "integrity": "sha512-iV/Az9SqwY2j5e/tZ7Fz7vwolfXp0xFTScET4lnS615oLQfdhyI2HOTg4CL4q7UVlSDW3HN4rCkDK2oASNVO7g==", "license": "MIT", "dependencies": { - "react-native-edge-to-edge": "1.6.0", - "react-native-is-edge-to-edge": "^1.1.6" + "react-native-is-edge-to-edge": "^1.2.1" }, "peerDependencies": { "react": "*", @@ -16454,18 +16235,34 @@ } }, "node_modules/expo-structured-headers": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/expo-structured-headers/-/expo-structured-headers-4.1.0.tgz", - "integrity": "sha512-2X+aUNzC/qaw7/WyUhrVHNDB0uQ5rE12XA2H/rJXaAiYQSuOeU90ladaN0IJYV9I2XlhYrjXLktLXWbO7zgbag==", + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/expo-structured-headers/-/expo-structured-headers-55.0.0.tgz", + "integrity": "sha512-udaNvuWb45/Sryq9FLC/blwgOChhznuqlTrUzVjC0T83pMdcmscKJX23lnNDW6hCec8p81Y3z1DIFwIyk0g/PQ==", "license": "MIT" }, + "node_modules/expo-symbols": { + "version": "55.0.3", + "resolved": "https://registry.npmjs.org/expo-symbols/-/expo-symbols-55.0.3.tgz", + "integrity": "sha512-r3AnrAAPw/zqha6dBdkJV7ueELy/yyeEKU38qzFFz37w1mhcXobuXahnfPSnY54lKrTbqrSiFMj/aGh25ElnoQ==", + "license": "MIT", + "dependencies": { + "@expo-google-fonts/material-symbols": "^0.4.1", + "sf-symbols-typescript": "^2.0.0" + }, + "peerDependencies": { + "expo": "*", + "expo-font": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-system-ui": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-5.0.11.tgz", - "integrity": "sha512-PG5VdaG5cwBe1Rj02mJdnsihKl9Iw/w/a6+qh2mH3f2K/IvQ+Hf7aG2kavSADtkGNCNj7CEIg7Rn4DQz/SE5rQ==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-55.0.5.tgz", + "integrity": "sha512-tcE0SJCfKZgESWUNBleqNIdgFZj6Y41WvgOpwMDhke9Fezwsput1MWz2L2QyHoeo1bUE5KuNuI5+nCWdecSnAQ==", "license": "MIT", "dependencies": { - "@react-native/normalize-colors": "0.79.6", + "@react-native/normalize-colors": "0.83.1", "debug": "^4.3.2" }, "peerDependencies": { @@ -16480,22 +16277,23 @@ } }, "node_modules/expo-updates": { - "version": "0.28.18", - "resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-0.28.18.tgz", - "integrity": "sha512-qbxRF8w/xPNiEWmZHDxK4XJ0ns4A9VpQg66jNeAEc8mrX9f7TG3TyLyLtydAP0F4aTNrBcSxah8K5VA90vcPig==", + "version": "55.0.7", + "resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-55.0.7.tgz", + "integrity": "sha512-AkpyXJ2nRfKSB1MF3qkHYQeuG7Or9FMWcQAbrkIzETGeA3jpfPo0APvuq5PJjZf1VDX2AN3zznmYjrQ/MSODTA==", "license": "MIT", "dependencies": { "@expo/code-signing-certificates": "^0.0.6", - "@expo/config": "~11.0.13", - "@expo/config-plugins": "~10.1.2", + "@expo/plist": "^0.5.2", "@expo/spawn-async": "^1.7.2", "arg": "4.1.0", "chalk": "^4.1.2", - "expo-eas-client": "~0.14.4", - "expo-manifests": "~0.16.6", - "expo-structured-headers": "~4.1.0", - "expo-updates-interface": "~1.1.0", - "glob": "^10.4.2", + "debug": "^4.3.4", + "expo-eas-client": "~55.0.2", + "expo-manifests": "~55.0.5", + "expo-structured-headers": "~55.0.0", + "expo-updates-interface": "~55.1.1", + "getenv": "^2.0.0", + "glob": "^13.0.0", "ignore": "^5.3.1", "resolve-from": "^5.0.0" }, @@ -16504,13 +16302,14 @@ }, "peerDependencies": { "expo": "*", - "react": "*" + "react": "*", + "react-native": "*" } }, "node_modules/expo-updates-interface": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-1.1.0.tgz", - "integrity": "sha512-DeB+fRe0hUDPZhpJ4X4bFMAItatFBUPjw/TVSbJsaf3Exeami+2qbbJhWkcTMoYHOB73nOIcaYcWXYJnCJXO0w==", + "version": "55.1.1", + "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-55.1.1.tgz", + "integrity": "sha512-++rlh9dNRUgm+uqN+x4vB/8ELidt0gc3kbwec55YzRizdD845kn5ufjTcGpkGs9SbAYHsP0eh8axlKHpY/v7Kg==", "license": "MIT", "peerDependencies": { "expo": "*" @@ -16522,244 +16321,73 @@ "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", "license": "MIT" }, + "node_modules/expo-updates/node_modules/balanced-match": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz", + "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==", + "license": "MIT", + "dependencies": { + "jackspeak": "^4.2.3" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/expo-updates/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/expo-updates/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.3.tgz", + "integrity": "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.2.0", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/expo-updates/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", + "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/expo/node_modules/@react-native/babel-plugin-codegen": { - "version": "0.79.6", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.79.6.tgz", - "integrity": "sha512-CS5OrgcMPixOyUJ/Sk/HSsKsKgyKT5P7y3CojimOQzWqRZBmoQfxdST4ugj7n1H+ebM2IKqbgovApFbqXsoX0g==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.79.6" - }, - "engines": { - "node": ">=18" - } + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0" }, - "node_modules/expo/node_modules/@react-native/babel-preset": { - "version": "0.79.6", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.79.6.tgz", - "integrity": "sha512-H+FRO+r2Ql6b5IwfE0E7D52JhkxjeGSBSUpCXAI5zQ60zSBJ54Hwh2bBJOohXWl4J+C7gKYSAd2JHMUETu+c/A==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.25.2", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.79.6", - "babel-plugin-syntax-hermes-parser": "0.25.1", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/expo/node_modules/@react-native/codegen": { - "version": "0.79.6", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.79.6.tgz", - "integrity": "sha512-iRBX8Lgbqypwnfba7s6opeUwVyaR23mowh9ILw7EcT2oLz3RqMmjJdrbVpWhGSMGq2qkPfqAH7bhO8C7O+xfjQ==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.25.3", - "glob": "^7.1.1", - "hermes-parser": "0.25.1", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "yargs": "^17.6.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/expo/node_modules/babel-plugin-react-native-web": { - "version": "0.19.13", - "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.19.13.tgz", - "integrity": "sha512-4hHoto6xaN23LCyZgL9LJZc3olmAxd7b6jDzlZnKXAh4rRAbZRKNBJoOOdp46OBqgy+K0t0guTj5/mhA8inymQ==", - "license": "MIT" - }, - "node_modules/expo/node_modules/babel-plugin-syntax-hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.25.1.tgz", - "integrity": "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ==", - "license": "MIT", - "dependencies": { - "hermes-parser": "0.25.1" - } - }, - "node_modules/expo/node_modules/babel-preset-expo": { - "version": "13.2.5", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-13.2.5.tgz", - "integrity": "sha512-YjVkP1bOLO2OgR2fyCedruYMPR7GFbAtCvvWITBW1UAp6e3ACYZtN6uoqkXgXP6PHQkb6M7qf2vZreBPEZK38A==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/plugin-proposal-decorators": "^7.12.9", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/preset-react": "^7.22.15", - "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-preset": "0.79.6", - "babel-plugin-react-native-web": "~0.19.13", - "babel-plugin-syntax-hermes-parser": "^0.25.1", - "babel-plugin-transform-flow-enums": "^0.0.2", - "debug": "^4.3.4", - "react-refresh": "^0.14.2", - "resolve-from": "^5.0.0" - }, - "peerDependencies": { - "babel-plugin-react-compiler": "^19.0.0-beta-e993439-20250405" - }, - "peerDependenciesMeta": { - "babel-plugin-react-compiler": { - "optional": true - } - } - }, - "node_modules/expo/node_modules/expo-font": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-13.3.2.tgz", - "integrity": "sha512-wUlMdpqURmQ/CNKK/+BIHkDA5nGjMqNlYmW0pJFXY/KE/OG80Qcavdu2sHsL4efAIiNGvYdBS10WztuQYU4X0A==", - "license": "MIT", - "dependencies": { - "fontfaceobserver": "^2.1.0" - }, - "peerDependencies": { - "expo": "*", - "react": "*" - } - }, - "node_modules/expo/node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", - "license": "MIT" - }, - "node_modules/expo/node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, - "node_modules/expo/node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0" - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "dev": true, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -16800,11 +16428,14 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", "dev": true, "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, "engines": { "node": ">= 16" }, @@ -17093,7 +16724,6 @@ "version": "0.5.8", "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", - "dev": true, "license": "(MIT OR Apache-2.0)", "bin": { "dotslash": "bin/dotslash" @@ -17132,6 +16762,35 @@ "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", "license": "MIT" }, + "node_modules/fbjs/node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/fbjs/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/fbjs/node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -17141,6 +16800,12 @@ "asap": "~2.0.3" } }, + "node_modules/fbjs/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/fbjs/node_modules/ua-parser-js": { "version": "1.0.41", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", @@ -17167,6 +16832,22 @@ "node": "*" } }, + "node_modules/fbjs/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/fbjs/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/fd-package-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", @@ -17218,6 +16899,12 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/fetch-nodeshim": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.6.tgz", + "integrity": "sha512-RP+zh0GZLp/7bUoS37+pN8zKIhqQ5YuI3m2RQfxZSqPzwYO1wRzwWqq4Va0kWpNPQbtpomEYAQ7zC/VjJWGmIw==", + "license": "MIT" + }, "node_modules/fflate": { "version": "0.4.8", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", @@ -17489,9 +17176,9 @@ "license": "MIT" }, "node_modules/flow-parser": { - "version": "0.299.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.299.0.tgz", - "integrity": "sha512-phGMRoNt6SNglPHGRbCyWm9/pxfe6t/t4++EIYPaBGWT6e0lphLBgUMrvpL62NbRo9R549o3oqrbKHq82kANCw==", + "version": "0.301.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.301.0.tgz", + "integrity": "sha512-yKRjJzN+W9dwk1tU9u6gowANdgFZyYknokcLePyLe30AV6WzAquKJzx+smN9wovWMBE2hvCDUvKbXUpcT1+8OA==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -17522,6 +17209,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -17538,6 +17226,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -17635,6 +17324,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/freeport-async/-/freeport-async-2.0.0.tgz", "integrity": "sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -17841,9 +17531,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz", - "integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==", + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -18053,19 +17743,25 @@ "node": ">= 0.4" } }, + "node_modules/hermes-compiler": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.14.0.tgz", + "integrity": "sha512-clxa193o+GYYwykWVFfpHduCATz8fR5jvU7ngXpfKHj+E9hr9vjLNtdLSEe8MUbObvVexV3wcyxQ00xTPIrB1Q==", + "license": "MIT" + }, "node_modules/hermes-estree": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.29.1.tgz", - "integrity": "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", "license": "MIT" }, "node_modules/hermes-parser": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.29.1.tgz", - "integrity": "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", "license": "MIT", "dependencies": { - "hermes-estree": "0.29.1" + "hermes-estree": "0.32.0" } }, "node_modules/hey-listen": { @@ -18090,12 +17786,11 @@ "license": "MIT" }, "node_modules/hono": { - "version": "4.11.7", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", - "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -18334,12 +18029,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, "node_modules/inline-style-prefixer": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", @@ -18372,6 +18061,16 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -18477,9 +18176,9 @@ } }, "node_modules/is-bun-module/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -18552,6 +18251,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -18968,9 +18668,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -19038,18 +18738,18 @@ } }, "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jest": { @@ -19289,25 +18989,24 @@ } }, "node_modules/jest-expo": { - "version": "53.0.14", - "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-53.0.14.tgz", - "integrity": "sha512-BwE5ZTjkhTvO+ejJBBJNlwar2YiahWjbcwhSPQ3oYV5UyvVdTrpKvZ+KjFv/7N5OaC3cOhv4/Ve4cTgS6m6vcw==", + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-55.0.6.tgz", + "integrity": "sha512-/s3VF8OcXe7ij5NTBszXIWhZEiiyFV+sju9GhPJvVSRMffTsuHj7G4g/lUVeBY5vFx6bOk75rlfVtlf+2PaqzA==", "dev": true, "license": "MIT", "dependencies": { - "@expo/config": "~11.0.13", - "@expo/json-file": "^9.1.5", + "@expo/config": "~55.0.4", + "@expo/json-file": "^10.0.12", "@jest/create-cache-key-function": "^29.2.1", "@jest/globals": "^29.2.1", "babel-jest": "^29.2.1", - "find-up": "^5.0.0", "jest-environment-jsdom": "^29.2.1", "jest-snapshot": "^29.2.1", "jest-watch-select-projects": "^2.0.0", "jest-watch-typeahead": "2.2.1", "json5": "^2.2.3", "lodash": "^4.17.19", - "react-test-renderer": "19.0.0", + "react-test-renderer": "19.2.0", "server-only": "^0.0.1", "stacktrace-js": "^2.0.2" }, @@ -19603,9 +19302,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -20026,12 +19725,6 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "license": "MIT" }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "license": "MIT" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -20163,9 +19856,9 @@ } }, "node_modules/knip": { - "version": "5.83.0", - "resolved": "https://registry.npmjs.org/knip/-/knip-5.83.0.tgz", - "integrity": "sha512-FfmaHMntpZB13B1oJQMSs1hTOZxd0TOn+FYB3oWEI02XlxTW3RH4H7d8z5Us3g0ziHCYyl7z0B1xi8ENP3QEKA==", + "version": "5.83.1", + "resolved": "https://registry.npmjs.org/knip/-/knip-5.83.1.tgz", + "integrity": "sha512-av3ZG/Nui6S/BNL8Tmj12yGxYfTnwWnslouW97m40him7o8MwiMjZBY9TPvlEWUci45aVId0/HbgTwSKIDGpMw==", "dev": true, "funding": [ { @@ -20273,13 +19966,14 @@ } }, "node_modules/libsql": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.4.7.tgz", - "integrity": "sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==", + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.22.tgz", + "integrity": "sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==", "cpu": [ "x64", "arm64", - "wasm32" + "wasm32", + "arm" ], "devOptional": true, "license": "MIT", @@ -20293,13 +19987,15 @@ "detect-libc": "2.0.2" }, "optionalDependencies": { - "@libsql/darwin-arm64": "0.4.7", - "@libsql/darwin-x64": "0.4.7", - "@libsql/linux-arm64-gnu": "0.4.7", - "@libsql/linux-arm64-musl": "0.4.7", - "@libsql/linux-x64-gnu": "0.4.7", - "@libsql/linux-x64-musl": "0.4.7", - "@libsql/win32-x64-msvc": "0.4.7" + "@libsql/darwin-arm64": "0.5.22", + "@libsql/darwin-x64": "0.5.22", + "@libsql/linux-arm-gnueabihf": "0.5.22", + "@libsql/linux-arm-musleabihf": "0.5.22", + "@libsql/linux-arm64-gnu": "0.5.22", + "@libsql/linux-arm64-musl": "0.5.22", + "@libsql/linux-x64-gnu": "0.5.22", + "@libsql/linux-x64-musl": "0.5.22", + "@libsql/win32-x64-msvc": "0.5.22" } }, "node_modules/lighthouse-logger": { @@ -20328,12 +20024,12 @@ "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.27.0.tgz", - "integrity": "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", "license": "MPL-2.0", "dependencies": { - "detect-libc": "^1.0.3" + "detect-libc": "^2.0.3" }, "engines": { "node": ">= 12.0.0" @@ -20343,29 +20039,30 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.27.0", - "lightningcss-darwin-x64": "1.27.0", - "lightningcss-freebsd-x64": "1.27.0", - "lightningcss-linux-arm-gnueabihf": "1.27.0", - "lightningcss-linux-arm64-gnu": "1.27.0", - "lightningcss-linux-arm64-musl": "1.27.0", - "lightningcss-linux-x64-gnu": "1.27.0", - "lightningcss-linux-x64-musl": "1.27.0", - "lightningcss-win32-arm64-msvc": "1.27.0", - "lightningcss-win32-x64-msvc": "1.27.0" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.27.0.tgz", - "integrity": "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==", + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", "cpu": [ "arm64" ], "license": "MPL-2.0", "optional": true, "os": [ - "darwin" + "android" ], "engines": { "node": ">= 12.0.0" @@ -20375,10 +20072,30 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz", - "integrity": "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", "cpu": [ "x64" ], @@ -20396,9 +20113,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz", - "integrity": "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", "cpu": [ "x64" ], @@ -20416,9 +20133,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz", - "integrity": "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", "cpu": [ "arm" ], @@ -20436,9 +20153,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz", - "integrity": "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", "cpu": [ "arm64" ], @@ -20456,9 +20173,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz", - "integrity": "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", "cpu": [ "arm64" ], @@ -20476,9 +20193,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz", - "integrity": "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", "cpu": [ "x64" ], @@ -20496,9 +20213,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz", - "integrity": "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", "cpu": [ "x64" ], @@ -20516,9 +20233,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz", - "integrity": "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", "cpu": [ "arm64" ], @@ -20536,9 +20253,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz", - "integrity": "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", "cpu": [ "x64" ], @@ -20556,15 +20273,12 @@ } }, "node_modules/lightningcss/node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/lilconfig": { @@ -20917,12 +20631,12 @@ } }, "node_modules/lucide-react-native": { - "version": "0.510.0", - "resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-0.510.0.tgz", - "integrity": "sha512-Ggy6NyWNcb/Wp9I0Kguwab5FRjr3GYsxNi0Nmy+skur1yj9TKiF2vcXPL7zWkkcgqk8WWKiIvmN3CntWvaDR1w==", + "version": "0.563.0", + "resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-0.563.0.tgz", + "integrity": "sha512-q4tYoAMorTqv+UXRYc0MyiEAOF+4Bu73zxD63EDrnGCFL+xuj+imBm3E2rIKRmME0heVHlK+98fsi8wbL92LNQ==", "license": "ISC", "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0", + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-native": "*", "react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0" } @@ -20943,9 +20657,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -21057,9 +20771,9 @@ } }, "node_modules/metro": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.82.5.tgz", - "integrity": "sha512-8oAXxL7do8QckID/WZEKaIFuQJFUTLzfVcC48ghkHhNK2RGuQq8Xvf4AVd+TUA0SZtX0q8TGNXZ/eba1ckeGCg==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.3.tgz", + "integrity": "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.24.7", @@ -21077,24 +20791,24 @@ "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.29.1", + "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.82.5", - "metro-cache": "0.82.5", - "metro-cache-key": "0.82.5", - "metro-config": "0.82.5", - "metro-core": "0.82.5", - "metro-file-map": "0.82.5", - "metro-resolver": "0.82.5", - "metro-runtime": "0.82.5", - "metro-source-map": "0.82.5", - "metro-symbolicate": "0.82.5", - "metro-transform-plugins": "0.82.5", - "metro-transform-worker": "0.82.5", + "metro-babel-transformer": "0.83.3", + "metro-cache": "0.83.3", + "metro-cache-key": "0.83.3", + "metro-config": "0.83.3", + "metro-core": "0.83.3", + "metro-file-map": "0.83.3", + "metro-resolver": "0.83.3", + "metro-runtime": "0.83.3", + "metro-source-map": "0.83.3", + "metro-symbolicate": "0.83.3", + "metro-transform-plugins": "0.83.3", + "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", @@ -21107,49 +20821,49 @@ "metro": "src/cli.js" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-babel-transformer": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.82.5.tgz", - "integrity": "sha512-W/scFDnwJXSccJYnOFdGiYr9srhbHPdxX9TvvACOFsIXdLilh3XuxQl/wXW6jEJfgIb0jTvoTlwwrqvuwymr6Q==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.3.tgz", + "integrity": "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.29.1", + "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-cache": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.82.5.tgz", - "integrity": "sha512-AwHV9607xZpedu1NQcjUkua8v7HfOTKfftl6Vc9OGr/jbpiJX6Gpy8E/V9jo/U9UuVYX2PqSUcVNZmu+LTm71Q==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.3.tgz", + "integrity": "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==", "license": "MIT", "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", - "metro-core": "0.82.5" + "metro-core": "0.83.3" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-cache-key": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.82.5.tgz", - "integrity": "sha512-qpVmPbDJuRLrT4kcGlUouyqLGssJnbTllVtvIgXfR7ZuzMKf0mGS+8WzcqzNK8+kCyakombQWR0uDd8qhWGJcA==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.3.tgz", + "integrity": "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-cache/node_modules/agent-base": { @@ -21175,114 +20889,42 @@ } }, "node_modules/metro-config": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.82.5.tgz", - "integrity": "sha512-/r83VqE55l0WsBf8IhNmc/3z71y2zIPe5kRSuqA5tY/SL/ULzlHUJEMd1szztd0G45JozLwjvrhAzhDPJ/Qo/g==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.3.tgz", + "integrity": "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==", "license": "MIT", "dependencies": { "connect": "^3.6.5", - "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", - "metro": "0.82.5", - "metro-cache": "0.82.5", - "metro-core": "0.82.5", - "metro-runtime": "0.82.5" - }, - "engines": { - "node": ">=18.18" - } - }, - "node_modules/metro-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/metro-config/node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "license": "MIT", - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/metro-config/node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "license": "MIT", - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/metro-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/metro-config/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "metro": "0.83.3", + "metro-cache": "0.83.3", + "metro-core": "0.83.3", + "metro-runtime": "0.83.3", + "yaml": "^2.6.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/metro-config/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=20.19.4" } }, "node_modules/metro-core": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.82.5.tgz", - "integrity": "sha512-OJL18VbSw2RgtBm1f2P3J5kb892LCVJqMvslXxuxjAPex8OH7Eb8RBfgEo7VZSjgb/LOf4jhC4UFk5l5tAOHHA==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.3.tgz", + "integrity": "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.82.5" + "metro-resolver": "0.83.3" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-file-map": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.82.5.tgz", - "integrity": "sha512-vpMDxkGIB+MTN8Af5hvSAanc6zXQipsAUO+XUx3PCQieKUfLwdoa8qaZ1WAQYRpaU+CJ8vhBcxtzzo3d9IsCIQ==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.3.tgz", + "integrity": "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -21296,20 +20938,20 @@ "walker": "^1.0.7" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-minify-terser": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.82.5.tgz", - "integrity": "sha512-v6Nx7A4We6PqPu/ta1oGTqJ4Usz0P7c+3XNeBxW9kp8zayS3lHUKR0sY0wsCHInxZlNAEICx791x+uXytFUuwg==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.3.tgz", + "integrity": "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-react-native-babel-preset": { @@ -21415,34 +21057,34 @@ } }, "node_modules/metro-resolver": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.82.5.tgz", - "integrity": "sha512-kFowLnWACt3bEsuVsaRNgwplT8U7kETnaFHaZePlARz4Fg8tZtmRDUmjaD68CGAwc0rwdwNCkWizLYpnyVcs2g==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.3.tgz", + "integrity": "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-runtime": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.82.5.tgz", - "integrity": "sha512-rQZDoCUf7k4Broyw3Ixxlq5ieIPiR1ULONdpcYpbJQ6yQ5GGEyYjtkztGD+OhHlw81LCR2SUAoPvtTus2WDK5g==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.3.tgz", + "integrity": "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-source-map": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.82.5.tgz", - "integrity": "sha512-wH+awTOQJVkbhn2SKyaw+0cd+RVSCZ3sHVgyqJFQXIee/yLs3dZqKjjeKKhhVeudgjXo7aE/vSu/zVfcQEcUfw==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.3.tgz", + "integrity": "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.3", @@ -21450,14 +21092,14 @@ "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.82.5", + "metro-symbolicate": "0.83.3", "nullthrows": "^1.1.1", - "ob1": "0.82.5", + "ob1": "0.83.3", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-source-map/node_modules/source-map": { @@ -21470,14 +21112,14 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.82.5.tgz", - "integrity": "sha512-1u+07gzrvYDJ/oNXuOG1EXSvXZka/0JSW1q2EYBWerVKMOhvv9JzDGyzmuV7hHbF2Hg3T3S2uiM36sLz1qKsiw==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.3.tgz", + "integrity": "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.82.5", + "metro-source-map": "0.83.3", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" @@ -21486,7 +21128,7 @@ "metro-symbolicate": "src/index.js" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-symbolicate/node_modules/source-map": { @@ -21499,9 +21141,9 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.82.5.tgz", - "integrity": "sha512-57Bqf3rgq9nPqLrT2d9kf/2WVieTFqsQ6qWHpEng5naIUtc/Iiw9+0bfLLWSAw0GH40iJ4yMjFcFJDtNSYynMA==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.3.tgz", + "integrity": "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -21512,13 +21154,13 @@ "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro-transform-worker": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.82.5.tgz", - "integrity": "sha512-mx0grhAX7xe+XUQH6qoHHlWedI8fhSpDGsfga7CpkO9Lk9W+aPitNtJWNGrW8PfjKEWbT9Uz9O50dkI8bJqigw==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.3.tgz", + "integrity": "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -21526,17 +21168,17 @@ "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "metro": "0.82.5", - "metro-babel-transformer": "0.82.5", - "metro-cache": "0.82.5", - "metro-cache-key": "0.82.5", - "metro-minify-terser": "0.82.5", - "metro-source-map": "0.82.5", - "metro-transform-plugins": "0.82.5", + "metro": "0.83.3", + "metro-babel-transformer": "0.83.3", + "metro-cache": "0.83.3", + "metro-cache-key": "0.83.3", + "metro-minify-terser": "0.83.3", + "metro-source-map": "0.83.3", + "metro-transform-plugins": "0.83.3", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/metro/node_modules/ci-info": { @@ -21686,6 +21328,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.1.2" @@ -21771,6 +21414,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multitars": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/multitars/-/multitars-0.2.4.tgz", + "integrity": "sha512-XgLbg1HHchFauMCQPRwMj6MSyDd5koPlTA1hM3rUFkeXzGpjU/I9fP3to7yrObE9jcN8ChIOQGrM0tV0kUZaKg==", + "license": "MIT" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -21853,12 +21502,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, - "node_modules/nested-error-stacks": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.0.1.tgz", - "integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==", - "license": "MIT" - }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -22002,9 +21645,9 @@ } }, "node_modules/npm-package-arg/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -22050,15 +21693,15 @@ "license": "MIT" }, "node_modules/ob1": { - "version": "0.82.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.82.5.tgz", - "integrity": "sha512-QyQQ6e66f+Ut/qUVjEce0E/wux5nAGLXYZDn1jr15JWstHsCH3l6VVrg8NKDptW9NEiBXKOJeGF/ydxeSDF3IQ==", + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.3.tgz", + "integrity": "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18.18" + "node": ">=20.19.4" } }, "node_modules/object-assign": { @@ -22312,35 +21955,35 @@ } }, "node_modules/oxc-resolver": { - "version": "11.17.0", - "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.17.0.tgz", - "integrity": "sha512-R5P2Tw6th+nQJdNcZGfuppBS/sM0x1EukqYffmlfX2xXLgLGCCPwu4ruEr9Sx29mrpkHgITc130Qps2JR90NdQ==", + "version": "11.17.1", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.17.1.tgz", + "integrity": "sha512-pyRXK9kH81zKlirHufkFhOFBZRks8iAMLwPH8gU7lvKFiuzUH9L8MxDEllazwOb8fjXMcWjY1PMDfMJ2/yh5cw==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-resolver/binding-android-arm-eabi": "11.17.0", - "@oxc-resolver/binding-android-arm64": "11.17.0", - "@oxc-resolver/binding-darwin-arm64": "11.17.0", - "@oxc-resolver/binding-darwin-x64": "11.17.0", - "@oxc-resolver/binding-freebsd-x64": "11.17.0", - "@oxc-resolver/binding-linux-arm-gnueabihf": "11.17.0", - "@oxc-resolver/binding-linux-arm-musleabihf": "11.17.0", - "@oxc-resolver/binding-linux-arm64-gnu": "11.17.0", - "@oxc-resolver/binding-linux-arm64-musl": "11.17.0", - "@oxc-resolver/binding-linux-ppc64-gnu": "11.17.0", - "@oxc-resolver/binding-linux-riscv64-gnu": "11.17.0", - "@oxc-resolver/binding-linux-riscv64-musl": "11.17.0", - "@oxc-resolver/binding-linux-s390x-gnu": "11.17.0", - "@oxc-resolver/binding-linux-x64-gnu": "11.17.0", - "@oxc-resolver/binding-linux-x64-musl": "11.17.0", - "@oxc-resolver/binding-openharmony-arm64": "11.17.0", - "@oxc-resolver/binding-wasm32-wasi": "11.17.0", - "@oxc-resolver/binding-win32-arm64-msvc": "11.17.0", - "@oxc-resolver/binding-win32-ia32-msvc": "11.17.0", - "@oxc-resolver/binding-win32-x64-msvc": "11.17.0" + "@oxc-resolver/binding-android-arm-eabi": "11.17.1", + "@oxc-resolver/binding-android-arm64": "11.17.1", + "@oxc-resolver/binding-darwin-arm64": "11.17.1", + "@oxc-resolver/binding-darwin-x64": "11.17.1", + "@oxc-resolver/binding-freebsd-x64": "11.17.1", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.17.1", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.17.1", + "@oxc-resolver/binding-linux-arm64-gnu": "11.17.1", + "@oxc-resolver/binding-linux-arm64-musl": "11.17.1", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.17.1", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.17.1", + "@oxc-resolver/binding-linux-riscv64-musl": "11.17.1", + "@oxc-resolver/binding-linux-s390x-gnu": "11.17.1", + "@oxc-resolver/binding-linux-x64-gnu": "11.17.1", + "@oxc-resolver/binding-linux-x64-musl": "11.17.1", + "@oxc-resolver/binding-openharmony-arm64": "11.17.1", + "@oxc-resolver/binding-wasm32-wasi": "11.17.1", + "@oxc-resolver/binding-win32-arm64-msvc": "11.17.1", + "@oxc-resolver/binding-win32-ia32-msvc": "11.17.1", + "@oxc-resolver/binding-win32-x64-msvc": "11.17.1" } }, "node_modules/p-limit": { @@ -22386,6 +22029,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -22527,9 +22171,9 @@ } }, "node_modules/patch-package/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -22607,26 +22251,29 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/path-to-regexp": { "version": "8.3.0", @@ -22959,9 +22606,9 @@ "license": "MIT" }, "node_modules/posthog-js": { - "version": "1.338.0", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.338.0.tgz", - "integrity": "sha512-Lnoz1YiSh1c3FbmwKYfCCeELHp1MWhYz6b5dEIX2YeDMvUt6xyeVy+MhpiA9xKs0Lbj5kGYk48kGt42qIlqq8A==", + "version": "1.347.2", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.347.2.tgz", + "integrity": "sha512-hDbsSU30gfNhC11cBYSPpwUYB4DglbCN2G8W8NPIR/KXhT03shmuxabra/uaoI4blkr8SSSpxwvYV4gGa3hXrA==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@opentelemetry/api": "^1.9.0", @@ -22969,8 +22616,8 @@ "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-logs": "^0.208.0", - "@posthog/core": "1.19.0", - "@posthog/types": "1.338.0", + "@posthog/core": "1.22.0", + "@posthog/types": "1.347.2", "core-js": "^3.38.1", "dompurify": "^3.3.1", "fflate": "^0.4.8", @@ -22980,12 +22627,12 @@ } }, "node_modules/posthog-react-native": { - "version": "4.29.0", - "resolved": "https://registry.npmjs.org/posthog-react-native/-/posthog-react-native-4.29.0.tgz", - "integrity": "sha512-IcRe8Z7y1WHcKD5MFjxYvycoGtRsn7GFFdm0UicsLPS9X5QcYfdLf06WIYQYYVJZyox8V8nBCd9AzbDMrrCq0A==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/posthog-react-native/-/posthog-react-native-4.34.0.tgz", + "integrity": "sha512-wJLIDA8S12C61rV02I8rLStMLdG4aP7/+b5Hn54KnqydWokdMaCvMhEI2ZlqYxqpQqD8CVGevwxRuIT1q6oeig==", "license": "MIT", "dependencies": { - "@posthog/core": "1.19.0" + "@posthog/core": "1.22.0" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.0.0", @@ -23038,13 +22685,10 @@ } }, "node_modules/posthog-react-native-session-replay": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/posthog-react-native-session-replay/-/posthog-react-native-session-replay-1.2.3.tgz", - "integrity": "sha512-4ZwdjeCldMveYAqSkTtppI3v4gXrcvoY5INNVy1G2/XJKWPGVrd+hvUwQR9UK/QaGBouE5CpkemADNu326z/aw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/posthog-react-native-session-replay/-/posthog-react-native-session-replay-1.2.4.tgz", + "integrity": "sha512-kPPDTJAHhEJBp5/aCNlO3bjBhcQlXmTOhrDfsZa1FuW44csldVK33DYA2Xr1eQDh/LW0xH3P3IupE+vpc9MRXQ==", "license": "MIT", - "workspaces": [ - "example" - ], "peerDependencies": { "react": "*", "react-native": "*" @@ -23097,13 +22741,13 @@ } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.6.14", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", - "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz", + "integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.21.3" + "node": ">=20.19" }, "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", @@ -23116,14 +22760,12 @@ "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", - "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", - "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "peerDependenciesMeta": { @@ -23154,9 +22796,6 @@ "prettier-plugin-css-order": { "optional": true }, - "prettier-plugin-import-sort": { - "optional": true - }, "prettier-plugin-jsdoc": { "optional": true }, @@ -23175,26 +22814,11 @@ "prettier-plugin-sort-imports": { "optional": true }, - "prettier-plugin-style-order": { - "optional": true - }, "prettier-plugin-svelte": { "optional": true } } }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -23375,9 +22999,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "devOptional": true, "license": "BSD-3-Clause", "dependencies": { @@ -23491,43 +23115,19 @@ "url": "https://opencollective.com/express" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-compiler-runtime": { - "version": "19.0.0-beta-ebf51a3-20250411", - "resolved": "https://registry.npmjs.org/react-compiler-runtime/-/react-compiler-runtime-19.0.0-beta-ebf51a3-20250411.tgz", - "integrity": "sha512-CetwBv7Wny+4Re6TZRPtVbNMx65CvLNDMkMp2KJ/pRmEc6WLPUoSSBuyY0bML9GPWiO/3LcWQ4iDGKitFU0qng==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/react-compiler-runtime/-/react-compiler-runtime-1.0.0.tgz", + "integrity": "sha512-rRfjYv66HlG8896yPUDONgKzG5BxZD1nV9U6rkm+7VCuvQc903C4MjcoZR4zPw53IKSOX9wMQVpA1IAbRtzQ7w==", "license": "MIT", "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental" @@ -23565,15 +23165,15 @@ } }, "node_modules/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", "dependencies": { - "scheduler": "^0.25.0" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.0.0" + "react": "^19.2.0" } }, "node_modules/react-error-boundary": { @@ -23633,57 +23233,56 @@ "license": "MIT" }, "node_modules/react-native": { - "version": "0.79.5", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.79.5.tgz", - "integrity": "sha512-jVihwsE4mWEHZ9HkO1J2eUZSwHyDByZOqthwnGrVZCh6kTQBCm4v8dicsyDa6p0fpWNE5KicTcpX/XXl0ASJFg==", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.83.1.tgz", + "integrity": "sha512-mL1q5HPq5cWseVhWRLl+Fwvi5z1UO+3vGOpjr+sHFwcUletPRZ5Kv+d0tUfqHmvi73/53NjlQqX1Pyn4GguUfA==", "license": "MIT", "dependencies": { "@jest/create-cache-key-function": "^29.7.0", - "@react-native/assets-registry": "0.79.5", - "@react-native/codegen": "0.79.5", - "@react-native/community-cli-plugin": "0.79.5", - "@react-native/gradle-plugin": "0.79.5", - "@react-native/js-polyfills": "0.79.5", - "@react-native/normalize-colors": "0.79.5", - "@react-native/virtualized-lists": "0.79.5", + "@react-native/assets-registry": "0.83.1", + "@react-native/codegen": "0.83.1", + "@react-native/community-cli-plugin": "0.83.1", + "@react-native/gradle-plugin": "0.83.1", + "@react-native/js-polyfills": "0.83.1", + "@react-native/normalize-colors": "0.83.1", + "@react-native/virtualized-lists": "0.83.1", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", - "babel-plugin-syntax-hermes-parser": "0.25.1", + "babel-plugin-syntax-hermes-parser": "0.32.0", "base64-js": "^1.5.1", - "chalk": "^4.0.0", "commander": "^12.0.0", - "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", + "hermes-compiler": "0.14.0", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.82.0", - "metro-source-map": "^0.82.0", + "metro-runtime": "^0.83.3", + "metro-source-map": "^0.83.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", - "react-devtools-core": "^6.1.1", + "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", - "scheduler": "0.25.0", + "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", - "ws": "^6.2.3", + "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "react-native": "cli.js" }, "engines": { - "node": ">=18" + "node": ">= 20.19.4" }, "peerDependencies": { - "@types/react": "^19.0.0", - "react": "^19.0.0" + "@types/react": "^19.1.1", + "react": "^19.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -23735,70 +23334,287 @@ } } }, - "node_modules/react-native-css-interop/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", + "node_modules/react-native-css-interop/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", "bin": { - "semver": "bin/semver.js" + "detect-libc": "bin/detect-libc.js" }, "engines": { - "node": ">=10" - } - }, - "node_modules/react-native-edge-to-edge": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/react-native-edge-to-edge/-/react-native-edge-to-edge-1.6.0.tgz", - "integrity": "sha512-2WCNdE3Qd6Fwg9+4BpbATUxCLcouF6YRY7K+J36KJ4l3y+tWN6XCqAC4DuoGblAAbb2sLkhEDp4FOlbOIot2Og==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" + "node": ">=0.10" } }, - "node_modules/react-native-element-dropdown": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/react-native-element-dropdown/-/react-native-element-dropdown-2.12.2.tgz", - "integrity": "sha512-Tf8hfRuniYEXo+LGoVgIMoItKWuPLX6jbqlwAFgMbBhmWGTuV+g1OVOAx/ny16kgnwp+NhgJoWpxhVvr7HSmXA==", - "license": "MIT", + "node_modules/react-native-css-interop/node_modules/lightningcss": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.27.0.tgz", + "integrity": "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==", + "license": "MPL-2.0", "dependencies": { - "lodash": "^4.17.21" + "detect-libc": "^1.0.3" }, "engines": { - "node": ">= 16.0.0" + "node": ">= 12.0.0" }, - "peerDependencies": { - "react": "*", - "react-native": "*" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.27.0", + "lightningcss-darwin-x64": "1.27.0", + "lightningcss-freebsd-x64": "1.27.0", + "lightningcss-linux-arm-gnueabihf": "1.27.0", + "lightningcss-linux-arm64-gnu": "1.27.0", + "lightningcss-linux-arm64-musl": "1.27.0", + "lightningcss-linux-x64-gnu": "1.27.0", + "lightningcss-linux-x64-musl": "1.27.0", + "lightningcss-win32-arm64-msvc": "1.27.0", + "lightningcss-win32-x64-msvc": "1.27.0" } }, - "node_modules/react-native-gesture-handler": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.24.0.tgz", - "integrity": "sha512-ZdWyOd1C8axKJHIfYxjJKCcxjWEpUtUWgTOVY2wynbiveSQDm8X/PDyAKXSer/GOtIpjudUbACOndZXCN3vHsw==", - "license": "MIT", + "node_modules/react-native-css-interop/node_modules/lightningcss-darwin-arm64": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.27.0.tgz", + "integrity": "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/react-native-css-interop/node_modules/lightningcss-darwin-x64": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz", + "integrity": "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/react-native-css-interop/node_modules/lightningcss-freebsd-x64": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz", + "integrity": "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/react-native-css-interop/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz", + "integrity": "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/react-native-css-interop/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz", + "integrity": "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/react-native-css-interop/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz", + "integrity": "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/react-native-css-interop/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz", + "integrity": "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/react-native-css-interop/node_modules/lightningcss-linux-x64-musl": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz", + "integrity": "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/react-native-css-interop/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz", + "integrity": "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/react-native-css-interop/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz", + "integrity": "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/react-native-css-interop/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native-element-dropdown": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/react-native-element-dropdown/-/react-native-element-dropdown-2.12.4.tgz", + "integrity": "sha512-abZc5SVji9FIt7fjojRYrbuvp03CoeZJrgvezQoDoSOrpiTqkX69ix5m+j06W2AVncA0VWvbT+vCMam8SoVadw==", + "license": "MIT", "dependencies": { - "@egjs/hammerjs": "^2.0.17", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4" + "lodash": "^4.17.21" + }, + "engines": { + "node": ">= 16.0.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, - "node_modules/react-native-haptic-feedback": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/react-native-haptic-feedback/-/react-native-haptic-feedback-2.3.3.tgz", - "integrity": "sha512-svS4D5PxfNv8o68m9ahWfwje5NqukM3qLS48+WTdhbDkNUkOhP9rDfDSRHzlhk4zq+ISjyw95EhLeh8NkKX5vQ==", + "node_modules/react-native-gesture-handler": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.30.0.tgz", + "integrity": "sha512-5YsnKHGa0X9C8lb5oCnKm0fLUPM6CRduvUUw2Bav4RIj/C3HcFh4RIUnF8wgG6JQWCL1//gRx4v+LVWgcIQdGA==", "license": "MIT", - "optional": true, - "workspaces": [ - "example" - ], + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4" + }, "peerDependencies": { - "react-native": ">=0.60.0" + "react": "*", + "react-native": "*" } }, "node_modules/react-native-is-edge-to-edge": { @@ -23812,9 +23628,9 @@ } }, "node_modules/react-native-keyboard-controller": { - "version": "1.20.7", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.20.7.tgz", - "integrity": "sha512-G8S5jz1FufPrcL1vPtReATx+jJhT/j+sTqxMIb30b1z7cYEfMlkIzOCyaHgf6IMB2KA9uBmnA5M6ve2A9Ou4kw==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.20.4.tgz", + "integrity": "sha512-IW2VatYwYhA3uRnGCOsEGARNjCYcYLUJX8KeuNz9GOMDWdLBVZWI+cy28+V4R2WvnToIIFku1NICbBSsQZi/8w==", "license": "MIT", "dependencies": { "react-native-is-edge-to-edge": "^1.2.1" @@ -23836,9 +23652,9 @@ } }, "node_modules/react-native-pager-view": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-7.0.2.tgz", - "integrity": "sha512-yj/v6BN/WGuV1VBVWaCjYOjQlhTaqJs4Ocismw0XRSsHGqO2wuQdWF8it5iFnfibQVBBED0/GC7qKOlQ4FBzNg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-8.0.0.tgz", + "integrity": "sha512-oAwlWT1lhTkIs9HhODnjNNl/owxzn9DP1MbP+az6OTUdgbmzA16Up83sBH8NRKwrH8rNm7iuWnX1qMqiiWOLhg==", "license": "MIT", "peerDependencies": { "react": "*", @@ -23846,25 +23662,24 @@ } }, "node_modules/react-native-reanimated": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.6.tgz", - "integrity": "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.1.tgz", + "integrity": "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==", "license": "MIT", "dependencies": { - "react-native-is-edge-to-edge": "^1.2.1", - "semver": "7.7.2" + "react-native-is-edge-to-edge": "1.2.1", + "semver": "7.7.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*", - "react-native-worklets": ">=0.5.0" + "react-native-worklets": ">=0.7.0" } }, "node_modules/react-native-reanimated/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -23889,9 +23704,9 @@ } }, "node_modules/react-native-safe-area-context": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz", - "integrity": "sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", + "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", "license": "MIT", "peerDependencies": { "react": "*", @@ -23899,13 +23714,12 @@ } }, "node_modules/react-native-screens": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.11.1.tgz", - "integrity": "sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.22.0.tgz", + "integrity": "sha512-f1AtzxmgZlKeoioWzdNdQ/4SuqZ/w370KCBnA666pN8UZ33X4PkVJ/9v9y2y5sj0cXKEcgeN3Br9lmeDRQ735A==", "license": "MIT", "dependencies": { "react-freeze": "^1.0.0", - "react-native-is-edge-to-edge": "^1.1.7", "warn-once": "^0.1.0" }, "peerDependencies": { @@ -23913,25 +23727,10 @@ "react-native": "*" } }, - "node_modules/react-native-sortables": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/react-native-sortables/-/react-native-sortables-1.9.4.tgz", - "integrity": "sha512-a6hxT+gl14HA5Sm8UiLXJqF8KMEQVa+mUJd75OnzoVsmrxUDtjAatlMdV0kI9qTQDT/ZSFLPRmdUhOR762IA4g==", - "license": "MIT", - "optionalDependencies": { - "react-native-haptic-feedback": ">=2.0.0" - }, - "peerDependencies": { - "react": "*", - "react-native": "*", - "react-native-gesture-handler": ">=2.0.0", - "react-native-reanimated": ">=3.0.0" - } - }, "node_modules/react-native-svg": { - "version": "15.15.2", - "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.2.tgz", - "integrity": "sha512-lpaSwA2i+eLvcEdDZyGgMEInQW99K06zjJqfMFblE0yxI0SCN5E4x6in46f0IYi6i3w2t2aaq3oOnyYBe+bo4w==", + "version": "15.15.1", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.1.tgz", + "integrity": "sha512-ZUD1xwc3Hwo4cOmOLumjJVoc7lEf9oQFlHnLmgccLC19fNm6LVEdtB+Cnip6gEi0PG3wfvVzskViEtrySQP8Fw==", "license": "MIT", "dependencies": { "css-select": "^5.1.0", @@ -23969,9 +23768,9 @@ } }, "node_modules/react-native-url-polyfill": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz", - "integrity": "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-3.0.0.tgz", + "integrity": "sha512-aA5CiuUCUb/lbrliVCJ6lZ17/RpNJzvTO/C7gC/YmDQhTUoRD5q5HlJfwLWcxz4VgAhHwXKzhxH+wUN24tAdqg==", "license": "MIT", "dependencies": { "whatwg-url-without-unicode": "8.0.0-3" @@ -24023,100 +23822,116 @@ "license": "MIT" }, "node_modules/react-native-worklets": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz", - "integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==", - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-arrow-functions": "^7.0.0-0", - "@babel/plugin-transform-class-properties": "^7.0.0-0", - "@babel/plugin-transform-classes": "^7.0.0-0", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", - "@babel/plugin-transform-optional-chaining": "^7.0.0-0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", - "@babel/plugin-transform-template-literals": "^7.0.0-0", - "@babel/plugin-transform-unicode-regex": "^7.0.0-0", - "@babel/preset-typescript": "^7.16.7", - "convert-source-map": "^2.0.0", - "semver": "7.7.2" + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.7.2.tgz", + "integrity": "sha512-DuLu1kMV/Uyl9pQHp3hehAlThoLw7Yk2FwRTpzASOmI+cd4845FWn3m2bk9MnjUw8FBRIyhwLqYm2AJaXDXsog==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "7.27.1", + "@babel/plugin-transform-class-properties": "7.27.1", + "@babel/plugin-transform-classes": "7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1", + "@babel/plugin-transform-optional-chaining": "7.27.1", + "@babel/plugin-transform-shorthand-properties": "7.27.1", + "@babel/plugin-transform-template-literals": "7.27.1", + "@babel/plugin-transform-unicode-regex": "7.27.1", + "@babel/preset-typescript": "7.27.1", + "convert-source-map": "2.0.0", + "semver": "7.7.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0", + "@babel/core": "*", "react": "*", "react-native": "*" } }, - "node_modules/react-native-worklets/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/react-native/node_modules/@react-native/codegen": { - "version": "0.79.5", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.79.5.tgz", - "integrity": "sha512-FO5U1R525A1IFpJjy+KVznEinAgcs3u7IbnbRJUG9IH/MBXi2lEU2LtN+JarJ81MCfW4V2p0pg6t/3RGHFRrlQ==", + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", "license": "MIT", "dependencies": { - "glob": "^7.1.1", - "hermes-parser": "0.25.1", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "yargs": "^17.6.2" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" }, "engines": { - "node": ">=18" + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "*" + "@babel/core": "^7.0.0-0" } }, - "node_modules/react-native/node_modules/@react-native/normalize-colors": { - "version": "0.79.5", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.79.5.tgz", - "integrity": "sha512-nGXMNMclZgzLUxijQQ38Dm3IAEhgxuySAWQHnljFtfB0JdaMwpe0Ox9H7Tp2OgrEA+EMEv+Od9ElKlHwGKmmvQ==", - "license": "MIT" - }, - "node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.25.1.tgz", - "integrity": "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ==", + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "license": "MIT", "dependencies": { - "hermes-parser": "0.25.1" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/react-native/node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", - "license": "MIT" - }, - "node_modules/react-native/node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "license": "MIT", "dependencies": { - "hermes-estree": "0.25.1" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/react-native/node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "node_modules/react-native-worklets/node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/react-native/node_modules/semver": { + "node_modules/react-native-worklets/node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", @@ -24128,22 +23943,53 @@ "node": ">=10" } }, - "node_modules/react-native/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", + "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", "license": "MIT", "dependencies": { - "async-limiter": "~1.0.0" + "hermes-parser": "0.32.0" + } + }, + "node_modules/react-native/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "dev": true, + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -24237,17 +24083,17 @@ } }, "node_modules/react-test-renderer": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.0.0.tgz", - "integrity": "sha512-oX5u9rOQlHzqrE/64CNr0HB0uWxkCQmZNSfozlYvwE71TLVgeZxVf0IjouGEr1v7r1kcDifdAJBeOhdhxsG/DA==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.0.tgz", + "integrity": "sha512-zLCFMHFE9vy/w3AxO0zNxy6aAupnCuLSVOJYDe/Tp+ayGI1f2PLQsFVPANSD42gdSbmYx5oN+1VWDhcXtq7hAQ==", "devOptional": true, "license": "MIT", "dependencies": { - "react-is": "^19.0.0", - "scheduler": "^0.25.0" + "react-is": "^19.2.0", + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.0.0" + "react": "^19.2.0" } }, "node_modules/react-timer-mixin": { @@ -24477,28 +24323,6 @@ "x-path": "^0.0.2" } }, - "node_modules/requireg": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/requireg/-/requireg-0.2.2.tgz", - "integrity": "sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==", - "dependencies": { - "nested-error-stacks": "~2.0.1", - "rc": "~1.2.7", - "resolve": "~1.7.1" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/requireg/node_modules/resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", - "license": "MIT", - "dependencies": { - "path-parse": "^1.0.5" - } - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -24756,9 +24580,9 @@ } }, "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/semver": { @@ -25024,6 +24848,32 @@ "node": ">=18.17" } }, + "node_modules/sharp-cli/node_modules/balanced-match": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz", + "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "jackspeak": "^4.2.3" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/sharp-cli/node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/sharp-cli/node_modules/glob": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", @@ -25049,57 +24899,14 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sharp-cli/node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sharp-cli/node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/sharp-cli/node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sharp-cli/node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", + "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" + "brace-expansion": "^5.0.2" }, "engines": { "node": "20 || >=22" @@ -25119,9 +24926,9 @@ } }, "node_modules/sharp/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -25617,30 +25424,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-width/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -25755,19 +25538,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -25834,17 +25604,17 @@ "license": "MIT" }, "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { @@ -25855,15 +25625,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/sucrase/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -25873,46 +25634,10 @@ "node": ">= 6" } }, - "node_modules/sucrase/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sucrase/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/supabase": { - "version": "2.75.1", - "resolved": "https://registry.npmjs.org/supabase/-/supabase-2.75.1.tgz", - "integrity": "sha512-7cIOjjUSxNNaVFAmtrVc9hQAzzFYj+FHzRWe7dOfw5SPTcyjsKDRsQM3FTHW/8O33BQITxVKCL3S/oXsfMV2bA==", + "version": "2.76.8", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-2.76.8.tgz", + "integrity": "sha512-Mhu3KNioxSjymdaHu6hQ/qZKzu/v1AzIEnYOOZ9+ATtSfrQNjqiMg9sivKx1vToKqcDBm3BXjmg8OX65sZW2Pw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -26144,6 +25869,7 @@ "version": "7.5.7", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -26160,6 +25886,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -26177,15 +25904,6 @@ "node": ">=6.0.0" } }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/temp/node_modules/rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -26461,9 +26179,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -26472,57 +26190,6 @@ "node": ">=10" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true, - "license": "MIT" - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -26583,13 +26250,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26600,13 +26266,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26617,13 +26282,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26634,13 +26298,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26651,13 +26314,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26668,13 +26330,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26685,13 +26346,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26702,13 +26362,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26719,13 +26378,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26736,13 +26394,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26753,13 +26410,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26770,13 +26426,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26787,13 +26442,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26804,13 +26458,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26821,13 +26474,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26838,13 +26490,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26855,13 +26506,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26872,13 +26522,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26889,13 +26538,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26906,13 +26554,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26923,13 +26570,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26940,13 +26586,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26957,13 +26602,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26974,13 +26618,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -26991,13 +26634,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -27008,13 +26650,12 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -27025,9 +26666,9 @@ } }, "node_modules/tsx/node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "devOptional": true, "hasInstallScript": true, "license": "MIT", @@ -27038,32 +26679,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/type-check": { @@ -27188,9 +26829,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -27201,16 +26842,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", - "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", + "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.54.0", - "@typescript-eslint/parser": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0" + "@typescript-eslint/eslint-plugin": "8.55.0", + "@typescript-eslint/parser": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -27272,15 +26913,16 @@ "version": "6.23.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.17" } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -27323,18 +26965,6 @@ "node": ">=4" } }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "license": "MIT", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -27522,13 +27152,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true, - "license": "MIT" - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -27704,6 +27327,12 @@ "node": ">=12" } }, + "node_modules/whatwg-url-minimum": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url-minimum/-/whatwg-url-minimum-0.1.1.tgz", + "integrity": "sha512-u2FNVjFVFZhdjb502KzXy1gKn1mEisQRJssmSJT8CPhZdZa0AP6VCbWlXERKyGu0l09t0k50FiDiralpGhBxgA==", + "license": "MIT" + }, "node_modules/whatwg-url-without-unicode": { "version": "8.0.0-3", "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", @@ -27834,12 +27463,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wonka": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.5.tgz", - "integrity": "sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==", - "license": "MIT" - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -27866,24 +27489,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -28012,7 +27617,6 @@ "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "devOptional": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -28051,16 +27655,6 @@ "node": ">=12" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -28093,16 +27687,16 @@ } }, "node_modules/zod-validation-error": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", - "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.4.tgz", + "integrity": "sha512-+hEiRIiPobgyuFlEojnqjJnhFvg4r/i3cqgcm67eehZf/WBaK3g6cD02YU9mtdVxZjv8CzCA9n/Rhrs3yAAvAw==", "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" }, "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" + "zod": "^3.24.4" } }, "node_modules/zustand": { diff --git a/package.json b/package.json index 5e9750144..4507a36dc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "langquest", "main": "expo-router/entry", - "version": "2.0.14", + "version": "2.1.0", "scripts": { "start": "expo start --dev-client", "start:offline:android": "expo start --dev-client -- --localhost --android", @@ -17,8 +17,9 @@ "export:analyze": "npx expo-atlas .expo/atlas.jsonl", "generate-env": "npx tsx ./scripts/generate-env.ts", "reset-project": "node ./scripts/reset-project.js", - "rebuild-android": "git clean -xdf node_modules package-lock.json android ios && npm i && npx expo prebuild --platform android && cd android && gradlew clean && cd ..", - "android": "expo run:android --device", + "clean-rebuild:android": "git clean -xdf node_modules package-lock.json android ios && npm i && npx expo prebuild --platform android && cd android && gradlew clean && cd ..", + "clean-rebuild:ios": "git clean -xdf node_modules package-lock.json && npm i && npx expo prebuild --platform ios", + "android": "expo run:android --variant debugOptimized --device", "android:preview": "npx cross-env EXPO_PUBLIC_APP_VARIANT=preview expo run:android --device --variant release", "android:release": "npx cross-env EXPO_PUBLIC_APP_VARIANT=production expo run:android --device --variant release", "ios": "expo run:ios --device", @@ -35,21 +36,21 @@ "typecheck": "tsc --noEmit", "powersync:start": "docker compose -f supabase/docker-compose.yml --env-file .env.local up -w", "powersync:stop": "docker compose -f supabase/docker-compose.yml --env-file .env.local down", - "env:start": "npm run supabase start && npm run powersync:start", - "env:stop": "npm run powersync:stop && npm run supabase stop", + "env:start": "npm run sb start && npm run powersync:start", + "env:stop": "npm run powersync:stop && npm run sb stop", "env": "npm run generate-env && npm run env:start && npm run env:stop", - "env:clean": "npm run supabase:clean && npm run powersync:clean", + "env:clean": "npm run sb:clean && npm run powersync:clean", "powersync:copy-assets": "npx powersync-web copy-assets", "powersync:diagnostics": "docker run --pull always -p 8082:80 --platform linux/amd64 journeyapps/powersync-diagnostics-app", "powersync:clean": "npm run powersync:stop && docker volume rm -f powersync_mongo_storage", - "supabase:clean": "git clean -xdf supabase/**/.DS_Store && npm run supabase db reset && npm run supabase stop", - "supabase": "npx supabase@beta", - "supabase:serve-functions": "npm run supabase functions serve -- --no-verify-jwt --debug", - "supabase:functions:deploy:send-email:dev": "npm run supabase functions deploy send-email -- --no-verify-jwt --project-ref yjgdgsycxmlvaiuynlbv", - "supabase:functions:deploy:send-email:prod": "npm run supabase functions deploy send-email -- --no-verify-jwt --project-ref unsxkmlcyxgtgmtzfonb", + "sb:clean": "git clean -xdf supabase/**/.DS_Store && npm run sb db reset && npm run sb stop", + "sb": "npx supabase@beta", + "sb:serve-functions": "npm run sb functions serve -- --no-verify-jwt --debug", + "sb:functions:deploy:send-email:dev": "npm run sb functions deploy send-email -- --no-verify-jwt --project-ref yjgdgsycxmlvaiuynlbv", + "sb:functions:deploy:send-email:prod": "npm run sb functions deploy send-email -- --no-verify-jwt --project-ref unsxkmlcyxgtgmtzfonb", "package-lock:fix": "npm install --package-lock-only", "shadcn": "shadcn", - "knip": "knip", + "knip:check": "knip", "postinstall": "patch-package", "adb:tunnel": "adb reverse tcp:8081 tcp:8081", "android:eas:build:preview": "eas build --platform android --profile preview" @@ -61,22 +62,22 @@ "@azure/core-asynciterator-polyfill": "^1.0.2", "@blazejkustra/react-native-alert": "^1.0.0", "@expo-google-fonts/noto-sans": "^0.4.2", - "@expo/vector-icons": "^14.0.2", "@gorhom/bottom-sheet": "^5.2.8", "@hookform/resolvers": "^5.0.1", + "@journeyapps/wa-sqlite": "^1.4.1", "@legendapp/list": "^2.0.12", - "@op-engineering/op-sqlite": "^14.1.4", + "@op-engineering/op-sqlite": "^15.2.5", "@powersync/attachments": "^2.4.0", - "@powersync/drizzle-driver": "^0.6.0", - "@powersync/op-sqlite": "^0.7.11", + "@powersync/drizzle-driver": "^0.7.2", + "@powersync/op-sqlite": "^0.8.0", "@powersync/react-native": "^1.25.1", "@powersync/tanstack-react-query": "^0.1.6", "@powersync/web": "^1.27.1", "@quidone/react-native-wheel-picker": "^1.6.1", - "@react-native-async-storage/async-storage": "2.1.2", + "@react-native-async-storage/async-storage": "2.2.0", "@react-native-community/netinfo": "11.4.1", - "@react-native-community/slider": "^5.1.1", - "@react-native-documents/picker": "^10.1.5", + "@react-native-community/slider": "5.1.1", + "@react-native-documents/picker": "^12.0.1", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.3.10", @@ -96,118 +97,115 @@ "@supabase/supabase-js": "^2.53.0", "@tanstack/react-query": "^5.84.0", "@tanstack/react-query-persist-client": "^5.84.0", - "babel-plugin-react-compiler": "^19.0.0-beta-af1b7da-20250417", + "babel-plugin-react-compiler": "^1.0.0", "base64-arraybuffer": "^1.0.2", "bidirectional-map": "^1.1.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "eslint-config-expo": "~9.2.0", + "eslint-config-expo": "~55.0.0", "eslint-plugin-drizzle": "^0.2.3", - "expo": "^53.0.20", - "expo-application": "~6.1.5", - "expo-av": "~15.1.7", - "expo-build-properties": "~0.14.8", - "expo-clipboard": "^8.0.7", - "expo-constants": "~17.1.7", - "expo-dev-client": "~5.2.4", - "expo-device": "~7.1.4", - "expo-drizzle-studio-plugin": "^0.2.0", - "expo-file-system": "~18.1.11", - "expo-haptics": "^15.0.7", - "expo-image": "~2.4.1", - "expo-json-utils": "^0.14.0", - "expo-linear-gradient": "~14.1.5", - "expo-linking": "~7.1.7", - "expo-localization": "~16.1.6", - "expo-manifests": "~0.16.6", - "expo-router": "~5.1.4", - "expo-sharing": "^14.0.7", - "expo-splash-screen": "~0.30.10", - "expo-status-bar": "~2.2.3", - "expo-system-ui": "~5.0.10", - "expo-updates": "~0.28.17", + "expo": "^55.0.0-preview.10", + "expo-application": "~55.0.5", + "expo-asset": "~55.0.4", + "expo-audio": "~55.0.5", + "expo-build-properties": "~55.0.6", + "expo-clipboard": "~55.0.5", + "expo-constants": "~55.0.4", + "expo-dev-client": "~55.0.5", + "expo-device": "~55.0.6", + "expo-drizzle-studio-plugin": "^0.2.1", + "expo-file-system": "~55.0.5", + "expo-font": "~55.0.3", + "expo-haptics": "~55.0.5", + "expo-image": "~55.0.3", + "expo-linear-gradient": "~55.0.5", + "expo-linking": "~55.0.4", + "expo-localization": "~55.0.5", + "expo-manifests": "~55.0.5", + "expo-router": "~55.0.0-preview.7", + "expo-sharing": "~55.0.6", + "expo-splash-screen": "~55.0.5", + "expo-sqlite": "~55.0.5", + "expo-status-bar": "~55.0.2", + "expo-system-ui": "~55.0.5", + "expo-updates": "~55.0.7", "js-logger": "^1.6.1", - "lucide-react-native": "^0.510.0", + "lucide-react-native": "^0.563.0", "moti": "^0.30.0", "nativewind": "^4.2.1", - "posthog-js": "^1.265.1", - "posthog-react-native": "^4.3.0", - "posthog-react-native-session-replay": "^1.1.1", - "react": "19.0.0", - "react-compiler-runtime": "^19.0.0-beta-af1b7da-20250417", - "react-dom": "19.0.0", - "react-hook-form": "^7.54.2", - "react-native": "0.79.5", + "posthog-js": "^1.345.1", + "posthog-react-native": "^4.31.0", + "posthog-react-native-session-replay": "^1.2.4", + "react": "19.2.0", + "react-compiler-runtime": "^1.0.0", + "react-dom": "19.2.0", + "react-hook-form": "^7.71.1", + "react-native": "0.83.1", "react-native-audio-concat": "^0.9.0", - "react-native-element-dropdown": "2.12.2", - "react-native-gesture-handler": "~2.24.0", - "react-native-keyboard-controller": "^1.19.5", + "react-native-element-dropdown": "2.12.4", + "react-native-gesture-handler": "~2.30.0", + "react-native-keyboard-controller": "1.20.4", "react-native-onboarding": "^1.0.6", - "react-native-pager-view": "^7.0.0", - "react-native-reanimated": "~4.1.0", + "react-native-pager-view": "8.0.0", + "react-native-reanimated": "^4.2.1", "react-native-reorderable-list": "^0.18.0", - "react-native-safe-area-context": "5.4.0", - "react-native-screens": "~4.11.1", - "react-native-sortables": "^1.9.4", - "react-native-svg": "^15.11.2", - "react-native-url-polyfill": "^2.0.0", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "4.22.0", + "react-native-svg": "15.15.1", + "react-native-url-polyfill": "^3.0.0", "react-native-uuid": "^2.0.3", "react-native-web": "^0.21.0", - "react-native-worklets": "0.5.1", - "tailwind-merge": "^3.3.0", + "react-native-worklets": "0.7.2", + "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", "testflight-dev-deploy": "^0.0.8", "vaul": "^1.1.2", - "zod": "^4.1.11", - "zustand": "^5.0.3" + "zod": "^4.3.6", + "zustand": "^5.0.11" }, "devDependencies": { "@babel/core": "^7.20.0", "@babel/plugin-transform-async-generator-functions": "^7.26.8", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@eslint/compat": "^1.2.4", - "@libsql/client": "^0.14.0", + "@libsql/client": "^0.17.0", "@modelcontextprotocol/sdk": "^1.0.4", "@react-native-community/cli": "latest", - "@react-native/debugger-frontend": "^0.83.1", - "@react-native/dev-middleware": "^0.83.1", "@total-typescript/ts-reset": "^0.6.1", "@types/bidirectional-map": "^1.0.4", "@types/default-gateway": "^7.2.2", - "@types/jest": "^29.5.12", - "@types/node": "^22.18.9", - "@types/react": "~19.0.10", + "@types/jest": "29.5.14", + "@types/node": "^25.2.2", + "@types/react": "~19.2.2", "@types/react-test-renderer": "^19.0.0", "babel-plugin-inline-import": "^3.0.0", - "babel-preset-expo": "^54.0.6", + "babel-preset-expo": "~55.0.0", "cross-env": "^10.1.0", "default-gateway": "^7.2.2", - "dotenv": "^16.4.7", - "drizzle-kit": "^0.31.4", - "drizzle-orm": "^0.44.4", + "drizzle-kit": "^0.31.9", + "drizzle-orm": "^0.45.1", "eslint": "^9.38.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-compiler": "^19.1.0-rc.2", "eslint-plugin-react-hooks": "^7.0.1", "expo-atlas": "^0.4.0", - "jest": "^29.2.1", - "jest-expo": "~53.0.9", + "jest": "~29.7.0", + "jest-expo": "~55.0.6", "jiti": "^2.6.1", - "knip": "^5.64.2", + "knip": "^5.83.1", "metro-react-native-babel-transformer": "^0.77.0", "patch-package": "^8.0.1", - "prettier": "^3.6.2", - "prettier-plugin-tailwindcss": "^0.6.14", + "prettier": "^3.8.1", + "prettier-plugin-tailwindcss": "^0.7.2", "react-native-svg-transformer": "^1.5.1", - "react-test-renderer": "19.0.0", + "react-test-renderer": "19.2.0", "sharp-cli": "^5.2.0", "supabase": "^2.48.3", - "tailwindcss": "^3.4.17", - "ts-node": "^10.9.2", - "tsx": "^4.19.2", - "typescript": "~5.8.3", - "typescript-eslint": "^8.20.0" + "tailwindcss": "^3.4.19", + "tsx": "^4.21.0", + "typescript": "~5.9.2", + "typescript-eslint": "^8.55.0" }, "overrides": { "ajv": "^6.12.6", @@ -218,18 +216,13 @@ } }, "schema-utils": "3.1.1", - "react": "19.0.0", - "react-dom": "19.0.0", - "@types/react": "~19.0.10", - "jest-snapshot-prettier": "npm:prettier@^3.6.2" - }, - "resolutions": { - "@react-native/dev-middleware": "0.83.0", - "@react-native/debugger-frontend": "0.83.0" - }, - "resolutionComments": { - "@react-native/dev-middleware": "Forcing resolution to use latest Chrome DevTools.", - "@react-native/debugger-frontend": "Forcing resolution to use latest Chrome DevTools." + "react": "19.2.0", + "react-dom": "19.2.0", + "@types/react": "~19.2.2", + "jest-snapshot-prettier": "npm:prettier@^3.6.2", + "expo-drizzle-studio-plugin": { + "expo": "^55.0.0-preview.10" + } }, "private": true } diff --git a/plugins/withAndroidConfig.js b/plugins/withAndroidConfig.js deleted file mode 100644 index 5e4a8efac..000000000 --- a/plugins/withAndroidConfig.js +++ /dev/null @@ -1,51 +0,0 @@ -const { - withAndroidManifest, - withGradleProperties, - withDangerousMod -} = require('@expo/config-plugins'); -const fs = require('fs/promises'); // Add this import - -function modifyBuildGradle(contents) { - // Replace minSdkVersion line - contents = contents.replace( - /minSdkVersion = Integer\.parseInt\(findProperty\('android\.minSdkVersion'\) \?: '\d+'\)/, - "minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '24')" - ); - - // Add ffmpegKitPackage if not present - if (!contents.includes('ffmpegKitPackage')) { - contents = contents.replace( - /ext {/, - `ext { - ffmpegKitPackage = "audio"` - ); - } - - return contents; -} - -const withAndroidConfig = (config) => { - // Ensure minSdkVersion in manifest - config = withAndroidManifest(config, async (config) => { - const androidManifest = config.modResults; - const mainApplication = androidManifest.manifest.$ || {}; - mainApplication['android:minSdkVersion'] = '24'; - return config; - }); - - // Modify build.gradle directly - config = withDangerousMod(config, [ - 'android', - async (config) => { - const buildGradlePath = 'android/build.gradle'; - const contents = await fs.readFile(buildGradlePath, 'utf-8'); - const newContents = modifyBuildGradle(contents); - await fs.writeFile(buildGradlePath, newContents); - return config; - } - ]); - - return config; -}; - -module.exports = withAndroidConfig; diff --git a/services/assetAudio.ts b/services/assetAudio.ts new file mode 100644 index 000000000..b8f29746b --- /dev/null +++ b/services/assetAudio.ts @@ -0,0 +1,592 @@ +/** + * AssetAudio - Centralized audio resolution, playback, waveform, and trim. + * + * See docs/asset-audio.md for full API documentation and usage examples. + * + * DEPENDENCIES (not yet created — will cause build errors until they exist): + * + * 1. `utils/audioWaveform.ts` must export `extractWaveformFromFile`: + * export function extractWaveformFromFile( + * uri: string, + * barCount: number, + * options?: { normalize?: boolean } + * ): Promise; + * + * 2. A `metadata TEXT` column on the `asset_content_link` table (both synced + * and local schemas). Until the migration runs, `parseTrimMetadata` will + * safely return `undefined`, and `saveTrim` will be a no-op since the + * column won't exist to write to. + */ + +import type { AudioSegment } from '@/contexts/AudioContext'; +import { useAudio } from '@/contexts/AudioContext'; +import { system } from '@/db/powersync/system'; +import { extractWaveformFromFile } from '@/utils/audioWaveform'; +import { resolveTable } from '@/utils/dbUtils'; +import { fileExists, getLocalAttachmentUriWithOPFS } from '@/utils/fileUtils'; +import { createAudioPlayer } from 'expo-audio'; +import { eq } from 'drizzle-orm'; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export interface AssetAudioSegment { + /** Local file URI */ + uri: string; + /** Full, untrimmed duration in ms */ + durationMs: number; + /** Trim points (relative to this segment's full duration) */ + trim?: { startMs: number; endMs: number }; + /** Content link row ID (for persisting trim back to DB) */ + contentLinkId: string; +} + +export interface AssetAudio { + assetId: string; + segments: AssetAudioSegment[]; + /** Sum of effective (trimmed) durations across all segments */ + totalDurationMs: number; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** + * Compute the effective duration of a segment, respecting trim if present. + */ +function effectiveDuration(seg: AssetAudioSegment): number { + if (seg.trim) { + return Math.max(0, seg.trim.endMs - seg.trim.startMs); + } + return seg.durationMs; +} + +/** + * Compute total effective duration across all segments. + */ +function computeTotalDuration(segments: AssetAudioSegment[]): number { + return segments.reduce((sum, seg) => sum + effectiveDuration(seg), 0); +} + +const waitForPlayerLoaded = async ( + player: ReturnType +) => { + if (player.isLoaded) return; + await new Promise((resolve) => { + const timeoutId = setTimeout(resolve, 5000); + const checkId = setInterval(() => { + if (!player.isLoaded) return; + clearInterval(checkId); + clearTimeout(timeoutId); + resolve(); + }, 10); + }); +}; + +/** + * Load duration for a single audio URI by briefly creating an expo-audio player. + */ +async function loadDuration(uri: string): Promise { + let player: ReturnType | null = null; + try { + player = createAudioPlayer(uri); + await waitForPlayerLoaded(player); + return player.isLoaded ? player.duration * 1000 : 0; + } catch { + return 0; + } finally { + player?.release(); + } +} + +/** + * Resolve a single audio value (local path, file URI, or attachment ID) to a + * verified local file URI. Returns null if the file can't be found. + * + * This is the single-source-of-truth for the three-way resolution that was + * previously duplicated across 6+ view files. + */ +async function resolveAudioValue( + audioValue: string, + contentLinksLocal: { asset_id: string; audio: string[] | null }[], + assetId: string +): Promise { + // --- Direct local path (from saveAudioLocally) --- + if (audioValue.startsWith('local/')) { + const constructedUri = await getLocalAttachmentUriWithOPFS(audioValue); + if (fileExists(constructedUri)) return constructedUri; + + // Fallback: search attachment queue + if (system.permAttachmentQueue) { + const filename = audioValue.replace(/^local\//, ''); + const uuidPart = filename.split('.')[0]; + const attachment = await system.powersync.getOptional<{ + id: string; + filename: string | null; + local_uri: string | null; + }>( + `SELECT * FROM ${system.permAttachmentQueue.table} WHERE filename = ? OR filename LIKE ? OR id = ? OR id LIKE ? LIMIT 1`, + [filename, `%${uuidPart}%`, filename, `%${uuidPart}%`] + ); + if (attachment?.local_uri) { + const foundUri = system.permAttachmentQueue.getLocalUri( + attachment.local_uri + ); + if (fileExists(foundUri)) return foundUri; + } + } + + // Fallback: try local table for file:// URIs + return findFallbackUri(contentLinksLocal, assetId); + } + + // --- Full file URI --- + if (audioValue.startsWith('file://')) { + if (fileExists(audioValue)) return audioValue; + + // Fallback: search attachment queue by filename + if (system.permAttachmentQueue) { + const filename = audioValue.split('/').pop(); + if (filename) { + const attachment = await system.powersync.getOptional<{ + id: string; + filename: string | null; + local_uri: string | null; + }>( + `SELECT * FROM ${system.permAttachmentQueue.table} WHERE filename = ? OR id = ? LIMIT 1`, + [filename, filename] + ); + if (attachment?.local_uri) { + const foundUri = system.permAttachmentQueue.getLocalUri( + attachment.local_uri + ); + if (fileExists(foundUri)) return foundUri; + } + } + } + return null; + } + + // --- Attachment ID --- + if (system.permAttachmentQueue) { + const attachment = await system.powersync.getOptional<{ + id: string; + local_uri: string | null; + }>(`SELECT * FROM ${system.permAttachmentQueue.table} WHERE id = ?`, [ + audioValue + ]); + if (attachment?.local_uri) { + const localUri = system.permAttachmentQueue.getLocalUri( + attachment.local_uri + ); + if (fileExists(localUri)) return localUri; + } + } + + // Fallback: try local table + return findFallbackUri(contentLinksLocal, assetId); +} + +/** + * Last-resort fallback: scan local content link rows for any working URI. + */ +async function findFallbackUri( + contentLinksLocal: { asset_id: string; audio: string[] | null }[], + assetId: string +): Promise { + const fallbackLink = contentLinksLocal.find( + (link) => link.asset_id === assetId + ); + if (!fallbackLink?.audio) return null; + + for (const value of fallbackLink.audio) { + if (value.startsWith('local/')) { + const uri = await getLocalAttachmentUriWithOPFS(value); + if (fileExists(uri)) return uri; + } else if (value.startsWith('file://')) { + if (fileExists(value)) return value; + } + } + return null; +} + +/** + * Parse trim metadata from a content link's metadata column. + * Until the DB migration adds the metadata column to asset_content_link, + * this will return undefined. + */ +function parseTrimMetadata(contentLink: { + metadata?: string | null; +}): { startMs: number; endMs: number } | undefined { + if (!contentLink.metadata) return undefined; + try { + const parsed = JSON.parse(contentLink.metadata) as { + trim?: { startMs: number; endMs: number }; + }; + if ( + parsed.trim && + typeof parsed.trim.startMs === 'number' && + typeof parsed.trim.endMs === 'number' + ) { + return parsed.trim; + } + } catch { + // Invalid JSON; ignore + } + return undefined; +} + +// --------------------------------------------------------------------------- +// Core: resolve +// --------------------------------------------------------------------------- + +/** + * Resolve an asset's audio into an AssetAudio object. + * Queries both synced and local content link tables, resolves each audio + * value to a verified local URI, loads durations, and reads trim metadata. + * + * @returns AssetAudio with populated segments, or null if no audio is found. + */ +export async function resolveAssetAudio( + assetId: string +): Promise { + // Query content links from synced and local tables + const syncedTable = resolveTable('asset_content_link', { + localOverride: false + }); + const localTable = resolveTable('asset_content_link', { + localOverride: true + }); + + const contentLinksSynced = await system.db + .select() + .from(syncedTable) + .where(eq(syncedTable.asset_id, assetId)); + const contentLinksLocal = await system.db + .select() + .from(localTable) + .where(eq(localTable.asset_id, assetId)); + + // Merge and deduplicate (synced wins) + const allLinks = [...contentLinksSynced, ...contentLinksLocal]; + const seenIds = new Set(); + const uniqueLinks = allLinks.filter((link) => { + if (seenIds.has(link.id)) return false; + seenIds.add(link.id); + return true; + }); + + if (uniqueLinks.length === 0) return null; + + // Build segments: one per audio value across all content links + // Process sequentially to avoid ExoPlayer threading issues on Android + const segments: AssetAudioSegment[] = []; + + for (const link of uniqueLinks) { + const audioArray = link.audio ?? []; + if (audioArray.length === 0) continue; + + // Read trim metadata from this content link row + const trim = parseTrimMetadata(link as { metadata?: string | null }); + + for (const audioValue of audioArray) { + if (!audioValue) continue; + + const uri = await resolveAudioValue( + audioValue, + contentLinksLocal as { + asset_id: string; + audio: string[] | null; + }[], + assetId + ); + if (!uri) continue; + + const durationMs = await loadDuration(uri); + + segments.push({ + uri, + durationMs, + trim, + contentLinkId: link.id + }); + } + } + + if (segments.length === 0) return null; + + return { + assetId, + segments, + totalDurationMs: computeTotalDuration(segments) + }; +} + +// --------------------------------------------------------------------------- +// Waveform +// --------------------------------------------------------------------------- + +/** + * Build a combined waveform for an asset's audio. + * + * By default, slices to the effective (trimmed) region of each segment. + * Pass `ignoreTrim: true` to get the full waveform for the entire + * underlying audio (used in the trim UI). + */ +export async function getAssetWaveform( + assetId: string, + options?: { barCount?: number; ignoreTrim?: boolean } +): Promise { + const barCount = options?.barCount ?? 128; + const ignoreTrim = options?.ignoreTrim ?? false; + + const audio = await resolveAssetAudio(assetId); + if (!audio || audio.segments.length === 0) return []; + + // Calculate the total duration we're representing + const totalDuration = ignoreTrim + ? audio.segments.reduce((sum, seg) => sum + seg.durationMs, 0) + : audio.totalDurationMs; + + if (totalDuration <= 0) return []; + + // Single segment, no trim to worry about + if (audio.segments.length === 1 && (ignoreTrim || !audio.segments[0]!.trim)) { + return extractWaveformFromFile(audio.segments[0]!.uri, barCount, { + normalize: true + }); + } + + // Multiple segments (or trimmed single segment): build proportional waveform + const merged: number[] = []; + let remainingBars = barCount; + let remainingWeight = totalDuration; + + for (let i = 0; i < audio.segments.length; i++) { + const seg = audio.segments[i]!; + const segDuration = ignoreTrim ? seg.durationMs : effectiveDuration(seg); + if (segDuration <= 0) continue; + + const isLast = i === audio.segments.length - 1; + let bars = isLast + ? remainingBars + : Math.max( + 1, + Math.round((remainingBars * segDuration) / remainingWeight) + ); + + // Ensure we leave at least 1 bar per remaining segment + const clipsLeft = audio.segments.length - i; + bars = Math.min(bars, Math.max(1, remainingBars - (clipsLeft - 1))); + + // Extract waveform for this segment + const segWaveform = await extractWaveformFromFile(seg.uri, bars, { + normalize: false + }); + + // If trimmed and not ignoring, slice the waveform to the trim region + if (!ignoreTrim && seg.trim && seg.durationMs > 0) { + const startFrac = seg.trim.startMs / seg.durationMs; + const endFrac = seg.trim.endMs / seg.durationMs; + const startBar = Math.floor(startFrac * segWaveform.length); + const endBar = Math.ceil(endFrac * segWaveform.length); + merged.push(...segWaveform.slice(startBar, endBar)); + } else { + merged.push(...segWaveform); + } + + remainingBars -= bars; + remainingWeight -= segDuration; + } + + // Normalize the combined waveform + const maxAmplitude = Math.max(...merged); + if (maxAmplitude > 0) { + for (let i = 0; i < merged.length; i++) { + merged[i] = merged[i]! / maxAmplitude; + } + } + + return merged; +} + +// --------------------------------------------------------------------------- +// Export +// --------------------------------------------------------------------------- + +/** + * Concatenate an asset's audio segments (with trim applied) into a single + * .m4a file and return the path. Delegates to the native audio-concat module. + * + * TODO: Wire up to react-native-audio-concat with trim offsets. + * For now, this is a placeholder that mirrors localAudioConcat.ts structure. + */ +export function exportAssetAudio(_assetId: string): Promise { + // const audio = await resolveAssetAudio(assetId); + // if (!audio) throw new Error('No audio found for asset'); + // + // 1. For each segment, apply trim (convert to temp .m4a if needed) + // 2. Concatenate via concatAudioFiles + // 3. Return output path + throw new Error('exportAssetAudio not yet implemented'); +} + +// --------------------------------------------------------------------------- +// Trim persistence +// --------------------------------------------------------------------------- + +/** + * Write each segment's trim points back to the asset_content_link metadata + * column in the database. + * + * Only updates local-only content links (synced content is immutable). + * + * NOTE: Requires the `metadata` column on `asset_content_link`. If the column + * does not exist yet (migration pending), the update will throw. The outer + * try/catch ensures the function degrades to a no-op in that case. + */ +export async function saveTrim(audio: AssetAudio): Promise { + const localTable = resolveTable('asset_content_link', { + localOverride: true + }); + const syncedTable = resolveTable('asset_content_link', { + localOverride: false + }); + + for (const seg of audio.segments) { + try { + // Only update local content links + const syncedRow = await system.db + .select({ id: syncedTable.id }) + .from(syncedTable) + .where(eq(syncedTable.id, seg.contentLinkId)) + .limit(1); + + if (syncedRow.length > 0) { + // Synced content is immutable; skip + continue; + } + + // Read existing metadata and merge in the trim + const existing = await system.db + .select() + .from(localTable) + .where(eq(localTable.id, seg.contentLinkId)) + .limit(1); + + if (existing.length === 0) continue; + + const row = existing[0] as { metadata?: string | null }; + let metadata: Record = {}; + if (row.metadata) { + try { + metadata = JSON.parse(row.metadata) as Record; + } catch { + // Invalid JSON; start fresh + } + } + + if (seg.trim) { + metadata.trim = { startMs: seg.trim.startMs, endMs: seg.trim.endMs }; + } else { + delete metadata.trim; + } + + const metadataStr = + Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null; + + await system.db + .update(localTable) + .set({ metadata: metadataStr } as Record) + .where(eq(localTable.id, seg.contentLinkId)); + } catch (error) { + console.warn( + `saveTrim: failed to update content link ${seg.contentLinkId}:`, + error + ); + } + } +} + +// --------------------------------------------------------------------------- +// Trim-aware playback (used by play() for trimmed segments) +// --------------------------------------------------------------------------- + +/** + * Convert AssetAudio segments into AudioSegment[] for AudioContext. + * Maps trim points to startMs/endMs. Segments without trim pass through + * as plain URIs with no offsets. + */ +function toAudioSegments(audio: AssetAudio): AudioSegment[] { + return audio.segments.map((seg) => ({ + uri: seg.uri, + startMs: seg.trim?.startMs, + endMs: seg.trim?.endMs + })); +} + +// --------------------------------------------------------------------------- +// Hook +// --------------------------------------------------------------------------- + +/** + * React hook for asset audio operations. + * + * Provides play, stop, resolve, getWaveform, export, saveTrim, and + * playback state. This is the main entry point for components. + * + * @example + * const audio = useAssetAudio(); + * await audio.play(assetId); + * await audio.stop(); + * const waveform = await audio.getWaveform(assetId, { barCount: 64 }); + */ +export function useAssetAudio() { + const ctx = useAudio(); + + const play = async (input: string | AssetAudio): Promise => { + const audio = + typeof input === 'string' ? await resolveAssetAudio(input) : input; + if (!audio || audio.segments.length === 0) return; + + // Convert to AudioSegments (with startMs/endMs from trim) and + // hand off to AudioContext. Single or multi, trimmed or not — + // playSoundSequence handles all cases uniformly. + const segments = toAudioSegments(audio); + if (segments.length === 1 && !segments[0]!.startMs && !segments[0]!.endMs) { + // Simple single untrimmed file — use playSound for least overhead + await ctx.playSound(segments[0]!.uri, audio.assetId); + } else { + await ctx.playSoundSequence(segments, audio.assetId); + } + }; + + return { + /** Play an asset by ID, or play a pre-resolved AssetAudio object. */ + play, + /** Stop current playback. */ + stop: ctx.stopCurrentSound, + /** Resolve when playback for this asset ID ends (natural end or stop). */ + waitForPlaybackEnd: ctx.waitForPlaybackEnd, + /** Resolve an asset's audio into an AssetAudio object. */ + resolve: resolveAssetAudio, + /** Build a combined waveform for an asset. */ + getWaveform: getAssetWaveform, + /** Export an asset's audio as a single .m4a file. */ + export: exportAssetAudio, + /** Persist trim points from an AssetAudio object to the database. */ + saveTrim, + + // Playback state + /** True while audio is playing. */ + isPlaying: ctx.isPlaying, + /** The asset ID currently playing, or null. */ + currentAudioId: ctx.currentAudioId, + /** Playback position in ms (Reanimated SharedValue, 60fps). */ + positionShared: ctx.positionShared, + /** Playback duration in ms (Reanimated SharedValue). */ + durationShared: ctx.durationShared + }; +} diff --git a/services/localizations.ts b/services/localizations.ts index 8892d3feb..797c0329e 100644 --- a/services/localizations.ts +++ b/services/localizations.ts @@ -5,7 +5,11 @@ export type SupportedLanguage = | 'brazilian_portuguese' | 'tok_pisin' | 'indonesian' - | 'nepali'; + | 'nepali' + | 'hindi' + | 'burmese' + | 'thai' + | 'mandarin'; /** * Languoid names that have local UI support. @@ -32,7 +36,21 @@ export const SUPPORTED_LANGUAGE_NAMES = new Set([ 'bahasa indonesia', // Nepali 'nepali', - 'नेपाली' + 'नेपाली', + // Hindi + 'hindi', + 'हिन्दी', + // Burmese + 'burmese', + 'မြန်မာ', + // Thai + 'thai', + 'ไทย', + // Mandarin + 'mandarin', + 'mandarin chinese', + '普通话', + '中文' ]); // Define the structure for translations @@ -49,7 +67,23 @@ export const localizations = { brazilian_portuguese: 'Aceitar', tok_pisin: 'Orait', indonesian: 'Terima', - nepali: 'स्वीकार गर्नुहोस्' + nepali: 'स्वीकार गर्नुहोस्', + hindi: 'स्वीकार करें', + burmese: 'လက်ခံပါ', + thai: 'ยอมรับ', + mandarin: '接受' + }, + iAgree: { + english: 'I agree', + spanish: 'Estoy de acuerdo', + brazilian_portuguese: 'Eu concordo', + tok_pisin: 'Mi agri', + indonesian: 'Saya setuju', + nepali: 'म सहमत छु', + hindi: 'मैं सहमत हूं', + burmese: 'ကျွန်ုပ် သဘောတူပါသည်', + thai: 'ฉันเห็นด้วย', + mandarin: '我同意' }, accountNotVerified: { english: @@ -63,7 +97,14 @@ export const localizations = { indonesian: 'Harap verifikasi alamat email Anda sebelum masuk. Periksa email Anda untuk tautan verifikasi.', nepali: - 'साइन इन गर्नु अघि कृपया आफ्नो इमेल ठेगाना प्रमाणित गर्नुहोस्। प्रमाणीकरण लिंकको लागि आफ्नो इमेल जाँच गर्नुहोस्।' + 'साइन इन गर्नु अघि कृपया आफ्नो इमेल ठेगाना प्रमाणित गर्नुहोस्। प्रमाणीकरण लिंकको लागि आफ्नो इमेल जाँच गर्नुहोस्।', + hindi: + 'कृपया साइन इन करने से पहले अपना ईमेल पता सत्यापित करें। सत्यापन लिंक के लिए अपना ईमेल जांचें।', + burmese: + 'အကောင့်ဝင်မီ သင်၏အီးမေးလ်လိပ်စာကို အတည်ပြုပါ။ အတည်ပြုချက်လင့်ခ်အတွက် သင်၏အီးမေးလ်ကို စစ်ဆေးပါ။', + thai: 'กรุณาตรวจสอบที่อยู่อีเมลของคุณก่อนเข้าสู่ระบบ ตรวจสอบอีเมลของคุณเพื่อลิงก์ยืนยัน', + mandarin: + '请在登录前验证您的电子邮件地址。请检查您的电子邮件以获取验证链接。' }, done: { english: 'Done', @@ -71,7 +112,11 @@ export const localizations = { brazilian_portuguese: 'Feito', tok_pisin: 'Done', indonesian: 'Selesai', - nepali: 'सम्पन्न' + nepali: 'सम्पन्न', + hindi: 'पूर्ण', + burmese: 'ပြီးပါပြီ', + thai: 'เสร็จสิ้น', + mandarin: '完成' }, all: { english: 'All', @@ -79,13 +124,23 @@ export const localizations = { brazilian_portuguese: 'Todos', tok_pisin: 'Olgeta', indonesian: 'Semua', - nepali: 'सबै' + nepali: 'सबै', + hindi: 'सभी', + burmese: 'အားလုံး', + thai: 'ทั้งหมด', + mandarin: '全部' }, options: { english: 'Options', spanish: 'Opciones', brazilian_portuguese: 'Opções', - nepali: 'विकल्पहरू' + tok_pisin: 'Ol options', + indonesian: 'Opsi', + nepali: 'विकल्पहरू', + hindi: 'विकल्प', + burmese: 'ရွေးချယ်စရာများ', + thai: 'ตัวเลือก', + mandarin: '选项' }, membersOnlyCreate: { english: 'Only project members can create content', @@ -93,7 +148,11 @@ export const localizations = { brazilian_portuguese: 'Apenas membros do projeto podem criar conteúdo', tok_pisin: 'Tasol ol memba bilong projek inap mekim nupela samting', indonesian: 'Hanya anggota proyek yang dapat membuat konten', - nepali: 'प्रोजेक्ट सदस्यहरूले मात्र सामग्री सिर्जना गर्न सक्छन्' + nepali: 'प्रोजेक्ट सदस्यहरूले मात्र सामग्री सिर्जना गर्न सक्छन्', + hindi: 'केवल परियोजना सदस्य ही सामग्री बना सकते हैं', + burmese: 'စီမံကိန်းအဖွဲ့ဝင်များသာ အကြောင်းအရာကို ဖန်တီးနိုင်သည်', + thai: 'เฉพาะสมาชิกโครงการเท่านั้นที่สามารถสร้างเนื้อหาได้', + mandarin: '只有项目成员可以创建内容' }, membersOnlyPublish: { english: 'Only project members can save to the cloud', @@ -101,7 +160,11 @@ export const localizations = { brazilian_portuguese: 'Apenas membros do projeto podem salvar na nuvem', tok_pisin: 'Tasol ol memba bilong projek inap save long cloud', indonesian: 'Hanya anggota proyek yang dapat menyimpan ke cloud', - nepali: 'प्रोजेक्ट सदस्यहरूले मात्र क्लाउडमा सेभ गर्न सक्छन्' + nepali: 'प्रोजेक्ट सदस्यहरूले मात्र क्लाउडमा सेभ गर्न सक्छन्', + hindi: 'केवल परियोजना सदस्य ही क्लाउड में सहेज सकते हैं', + burmese: 'စီမံကိန်းအဖွဲ့ဝင်များသာ cloud သို့ သိမ်းဆည်းနိုင်သည်', + thai: 'เฉพาะสมาชิกโครงการเท่านั้นที่สามารถบันทึกลงคลาวด์ได้', + mandarin: '只有项目成员可以保存到云端' }, apply: { english: 'Apply', @@ -109,7 +172,11 @@ export const localizations = { brazilian_portuguese: 'Aplicar', tok_pisin: 'Putim', indonesian: 'Terapkan', - nepali: 'लागू गर्नुहोस्' + nepali: 'लागू गर्नुहोस्', + hindi: 'लागू करें', + burmese: 'အသုံးပြုပါ', + thai: 'ใช้', + mandarin: '应用' }, avatar: { english: 'Avatar', @@ -117,7 +184,11 @@ export const localizations = { brazilian_portuguese: 'Avatar', tok_pisin: 'Avatar', indonesian: 'Avatar', - nepali: 'अवतार' + nepali: 'अवतार', + hindi: 'अवतार', + burmese: 'ပုံရိပ်', + thai: 'อวตาร', + mandarin: '头像' }, backToLogin: { english: 'Back to Login', @@ -125,7 +196,11 @@ export const localizations = { brazilian_portuguese: 'Voltar para o Login', tok_pisin: 'Go bek long Login', indonesian: 'Kembali ke Login', - nepali: 'लगइनमा फर्कनुहोस्' + nepali: 'लगइनमा फर्कनुहोस्', + hindi: 'लॉगिन पर वापस जाएं', + burmese: 'အကောင့်ဝင်သို့ ပြန်သွားပါ', + thai: 'กลับไปที่การเข้าสู่ระบบ', + mandarin: '返回登录' }, checkEmail: { english: 'Please check your email', @@ -133,7 +208,11 @@ export const localizations = { brazilian_portuguese: 'Por favor, verifique seu e-mail', tok_pisin: 'Plis checkum email bilong yu', indonesian: 'Silakan periksa email Anda', - nepali: 'कृपया आफ्नो इमेल जाँच गर्नुहोस्' + nepali: 'कृपया आफ्नो इमेल जाँच गर्नुहोस्', + hindi: 'कृपया अपना ईमेल जांचें', + burmese: 'သင်၏အီးမေးလ်ကို စစ်ဆေးပါ', + thai: 'กรุณาตรวจสอบอีเมลของคุณ', + mandarin: '请检查您的电子邮件' }, checkEmailForResetLink: { english: 'Please check your email for the password reset link', @@ -143,7 +222,11 @@ export const localizations = { 'Por favor, verifique seu e-mail para o link de redefinição de senha', tok_pisin: 'Plis checkum email bilong yu long password reset link', indonesian: 'Silakan periksa email Anda untuk tautan reset kata sandi', - nepali: 'कृपया पासवर्ड रिसेट लिंकको लागि आफ्नो इमेल जाँच गर्नुहोस्' + nepali: 'कृपया पासवर्ड रिसेट लिंकको लागि आफ्नो इमेल जाँच गर्नुहोस्', + hindi: 'कृपया पासवर्ड रीसेट लिंक के लिए अपना ईमेल जांचें', + burmese: 'စကားဝှက်ပြန်လည်သတ်မှတ်ရန်လင့်ခ်အတွက် သင်၏အီးမေးလ်ကို စစ်ဆေးပါ', + thai: 'กรุณาตรวจสอบอีเมลของคุณเพื่อลิงก์รีเซ็ตรหัสผ่าน', + mandarin: '请检查您的电子邮件以获取密码重置链接' }, confirmNewPassword: { english: 'Confirm New Password', @@ -151,7 +234,11 @@ export const localizations = { brazilian_portuguese: 'Confirmar Nova Senha', tok_pisin: 'Confirm nupela password', indonesian: 'Konfirmasi Kata Sandi Baru', - nepali: 'नयाँ पासवर्ड पुष्टि गर्नुहोस्' + nepali: 'नयाँ पासवर्ड पुष्टि गर्नुहोस्', + hindi: 'नया पासवर्ड पुष्टि करें', + burmese: 'စကားဝှက်အသစ်ကို အတည်ပြုပါ', + thai: 'ยืนยันรหัสผ่านใหม่', + mandarin: '确认新密码' }, confirmPassword: { english: 'Confirm Password', @@ -159,7 +246,11 @@ export const localizations = { brazilian_portuguese: 'Confirmar Senha', tok_pisin: 'Confirm password', indonesian: 'Konfirmasi Kata Sandi', - nepali: 'पासवर्ड पुष्टि गर्नुहोस्' + nepali: 'पासवर्ड पुष्टि गर्नुहोस्', + hindi: 'पासवर्ड पुष्टि करें', + burmese: 'စကားဝှက်ကို အတည်ပြုပါ', + thai: 'ยืนยันรหัสผ่าน', + mandarin: '确认密码' }, createObject: { english: 'Create', @@ -167,7 +258,11 @@ export const localizations = { brazilian_portuguese: 'Criar', tok_pisin: 'Create', indonesian: 'Buat', - nepali: 'सिर्जना गर्नुहोस्' + nepali: 'सिर्जना गर्नुहोस्', + hindi: 'बनाएं', + burmese: 'ဖန်တီးပါ', + thai: 'สร้าง', + mandarin: '创建' }, projectName: { english: 'Project Name', @@ -175,7 +270,11 @@ export const localizations = { brazilian_portuguese: 'Nome do Projeto', tok_pisin: 'Project Name', indonesian: 'Nama Proyek', - nepali: 'प्रोजेक्टको नाम' + nepali: 'प्रोजेक्टको नाम', + hindi: 'परियोजना का नाम', + burmese: 'စီမံကိန်းအမည်', + thai: 'ชื่อโครงการ', + mandarin: '项目名称' }, newProject: { english: 'New Project', @@ -183,15 +282,35 @@ export const localizations = { brazilian_portuguese: 'Novo Projeto', tok_pisin: 'Nupela Project', indonesian: 'Proyek Baru', - nepali: 'नयाँ प्रोजेक्ट' + nepali: 'नयाँ प्रोजेक्ट', + hindi: 'नई परियोजना', + burmese: 'စီမံကိန်းအသစ်', + thai: 'โครงการใหม่', + mandarin: '新项目' }, newQuest: { english: 'New Quest', - nepali: 'नयाँ क्वेस्ट' + spanish: 'Nueva Misión', + brazilian_portuguese: 'Nova Missão', + tok_pisin: 'Nupela Quest', + indonesian: 'Quest Baru', + nepali: 'नयाँ क्वेस्ट', + hindi: 'नया क्वेस्ट', + burmese: 'Quest အသစ်', + thai: 'เควสต์ใหม่', + mandarin: '新任务' }, questName: { english: 'Quest Name', - nepali: 'क्वेस्टको नाम' + spanish: 'Nombre de la Misión', + brazilian_portuguese: 'Nome da Missão', + tok_pisin: 'Nem bilong Quest', + indonesian: 'Nama Quest', + nepali: 'क्वेस्टको नाम', + hindi: 'क्वेस्ट का नाम', + burmese: 'Quest အမည်', + thai: 'ชื่อเควสต์', + mandarin: '任务名称' }, description: { english: 'Description', @@ -199,7 +318,11 @@ export const localizations = { brazilian_portuguese: 'Descrição', tok_pisin: 'Description', indonesian: 'Deskripsi', - nepali: 'विवरण' + nepali: 'विवरण', + hindi: 'विवरण', + burmese: 'ဖော်ပြချက်', + thai: 'คำอธิบาย', + mandarin: '描述' }, visible: { english: 'Visible', @@ -207,7 +330,11 @@ export const localizations = { brazilian_portuguese: 'Visible', tok_pisin: 'Visible', indonesian: 'Visible', - nepali: 'देखिने' + nepali: 'देखिने', + hindi: 'दृश्यमान', + burmese: 'မြင်နိုင်သည်', + thai: 'มองเห็นได้', + mandarin: '可见' }, private: { english: 'Private', @@ -215,7 +342,11 @@ export const localizations = { brazilian_portuguese: 'Privado', tok_pisin: 'Private', indonesian: 'Private', - nepali: 'निजी' + nepali: 'निजी', + hindi: 'निजी', + burmese: 'ကိုယ်ပိုင်', + thai: 'ส่วนตัว', + mandarin: '私有' }, date: { english: 'Date', @@ -223,7 +354,11 @@ export const localizations = { brazilian_portuguese: 'Data', tok_pisin: 'De', indonesian: 'Tanggal', - nepali: 'मिति' + nepali: 'मिति', + hindi: 'तारीख', + burmese: 'ရက်စွဲ', + thai: 'วันที่', + mandarin: '日期' }, decline: { english: 'Decline', @@ -231,7 +366,11 @@ export const localizations = { brazilian_portuguese: 'Rejeitar', tok_pisin: 'No', indonesian: 'Tolak', - nepali: 'अस्वीकार गर्नुहोस्' + nepali: 'अस्वीकार गर्नुहोस्', + hindi: 'अस्वीकार करें', + burmese: 'ငြင်းဆိုပါ', + thai: 'ปฏิเสธ', + mandarin: '拒绝' }, downloadAnyway: { english: 'Download Anyway', @@ -239,7 +378,11 @@ export const localizations = { brazilian_portuguese: 'Descarregar de qualquer forma', tok_pisin: 'Download tasol', indonesian: 'Unduh Saja', - nepali: 'जे भए पनि डाउनलोड गर्नुहोस्' + nepali: 'जे भए पनि डाउनलोड गर्नुहोस्', + hindi: 'वैसे भी डाउनलोड करें', + burmese: 'ဘာပဲဖြစ်ဖြစ် ဒေါင်းလုဒ်လုပ်ပါ', + thai: 'ดาวน์โหลดต่อไป', + mandarin: '仍然下载' }, downloadProject: { english: 'Download Project', @@ -247,7 +390,11 @@ export const localizations = { brazilian_portuguese: 'Descarregar Projeto', tok_pisin: 'Download project', indonesian: 'Unduh Proyek', - nepali: 'प्रोजेक्ट डाउनलोड गर्नुहोस्' + nepali: 'प्रोजेक्ट डाउनलोड गर्नुहोस्', + hindi: 'परियोजना डाउनलोड करें', + burmese: 'စီမံကိန်းကို ဒေါင်းလုဒ်လုပ်ပါ', + thai: 'ดาวน์โหลดโครงการ', + mandarin: '下载项目' }, downloadQuest: { english: 'Download Quest', @@ -255,7 +402,11 @@ export const localizations = { brazilian_portuguese: 'Descarregar Quest', nepali: 'क्वेस्ट डाउनलोड गर्नुहोस्', tok_pisin: 'Download quest', - indonesian: 'Unduh Quest' + indonesian: 'Unduh Quest', + hindi: 'क्वेस्ट डाउनलोड करें', + burmese: 'Quest ကို ဒေါင်းလုဒ်လုပ်ပါ', + thai: 'ดาวน์โหลดเควสต์', + mandarin: '下载任务' }, downloadProjectOfflineWarning: { english: @@ -269,7 +420,14 @@ export const localizations = { indonesian: 'Jika Anda tidak mengunduh proyek, Anda tidak akan dapat berkontribusi secara offline. Anda dapat mengunduhnya nanti dengan menekan tombol unduh di kartu proyek.', nepali: - 'यदि तपाईंले प्रोजेक्ट डाउनलोड गर्नुभएन भने, तपाईं अफलाइन योगदान गर्न सक्षम हुनुहुने छैन। तपाईं पछि प्रोजेक्ट कार्डको डाउनलोड बटन थिचेर डाउनलोड गर्न सक्नुहुन्छ।' + 'यदि तपाईंले प्रोजेक्ट डाउनलोड गर्नुभएन भने, तपाईं अफलाइन योगदान गर्न सक्षम हुनुहुने छैन। तपाईं पछि प्रोजेक्ट कार्डको डाउनलोड बटन थिचेर डाउनलोड गर्न सक्नुहुन्छ।', + hindi: + 'यदि आप परियोजना डाउनलोड नहीं करते हैं, तो आप ऑफलाइन योगदान नहीं दे सकेंगे। आप बाद में परियोजना कार्ड के डाउनलोड बटन दबाकर इसे डाउनलोड कर सकते हैं।', + burmese: + 'သင်သည် စီမံကိန်းကို ဒေါင်းလုဒ်မလုပ်ပါက၊ အင်တာနက်မရှိသောအချိန်တွင် ပံ့ပိုးမပေးနိုင်ပါ။ သင်သည် နောက်ပိုင်းတွင် စီမံကိန်းကတ်တွင် ဒေါင်းလုဒ်ခလုတ်ကို နှိပ်ခြင်းဖြင့် ဒေါင်းလုဒ်လုပ်နိုင်သည်။', + thai: 'หากคุณไม่ดาวน์โหลดโครงการ คุณจะไม่สามารถมีส่วนร่วมแบบออฟไลน์ได้ คุณสามารถดาวน์โหลดได้ในภายหลังโดยกดปุ่มดาวน์โหลดบนการ์ดโครงการ', + mandarin: + '如果您不下载项目,您将无法离线贡献。您可以稍后通过按项目卡上的下载按钮来下载它。' }, downloadProjectWhenRequestSent: { english: 'Download project when request is sent', @@ -277,7 +435,11 @@ export const localizations = { brazilian_portuguese: 'Baixar projeto quando a solicitação for enviada', tok_pisin: 'Download project taim request i go', indonesian: 'Unduh proyek saat permintaan dikirim', - nepali: 'अनुरोध पठाइँदा प्रोजेक्ट डाउनलोड गर्नुहोस्' + nepali: 'अनुरोध पठाइँदा प्रोजेक्ट डाउनलोड गर्नुहोस्', + hindi: 'अनुरोध भेजे जाने पर परियोजना डाउनलोड करें', + burmese: 'တောင်းဆိုမှုပို့သောအခါ စီမံကိန်းကို ဒေါင်းလုဒ်လုပ်ပါ', + thai: 'ดาวน์โหลดโครงการเมื่อส่งคำขอ', + mandarin: '发送请求时下载项目' }, discoveringQuestData: { english: 'Discovering Quest Data', @@ -285,7 +447,11 @@ export const localizations = { brazilian_portuguese: 'Descobrindo Dados da Missão', tok_pisin: 'Painimaut long Quest Data', indonesian: 'Menemukan Data Quest', - nepali: 'क्वेस्ट डाटा खोज्दै' + nepali: 'क्वेस्ट डाटा खोज्दै', + hindi: 'क्वेस्ट डेटा खोज रहे हैं', + burmese: 'Quest ဒေတာကို ရှာဖွေနေသည်', + thai: 'กำลังค้นหาข้อมูลเควสต์', + mandarin: '正在发现任务数据' }, offloadQuest: { english: 'Offload Quest', @@ -293,7 +459,11 @@ export const localizations = { brazilian_portuguese: 'Descarregar Quest', tok_pisin: 'Rausim Quest', indonesian: 'Lepas Quest', - nepali: 'क्वेस्ट हटाउनुहोस्' + nepali: 'क्वेस्ट हटाउनुहोस्', + hindi: 'क्वेस्ट अनलोड करें', + burmese: 'Quest ကို ဖယ်ရှားပါ', + thai: 'ถอดเควสต์', + mandarin: '卸载任务' }, offloadQuestDescription: { english: 'Remove local data to free up storage', @@ -301,7 +471,11 @@ export const localizations = { brazilian_portuguese: 'Remover dados locais para liberar armazenamento', tok_pisin: 'Rausim data long freeup storage', indonesian: 'Hapus data lokal untuk membebaskan penyimpanan', - nepali: 'भण्डारण खाली गर्न स्थानीय डाटा हटाउनुहोस्' + nepali: 'भण्डारण खाली गर्न स्थानीय डाटा हटाउनुहोस्', + hindi: 'भंडारण खाली करने के लिए स्थानीय डेटा हटाएं', + burmese: 'သိုလှောင်မှုကို လွတ်လပ်စေရန် ဒေသတွင်းဒေတာကို ဖယ်ရှားပါ', + thai: 'ลบข้อมูลท้องถิ่นเพื่อเพิ่มพื้นที่เก็บข้อมูล', + mandarin: '删除本地数据以释放存储空间' }, verifyingCloudData: { english: 'Verifying data in cloud...', @@ -309,7 +483,11 @@ export const localizations = { brazilian_portuguese: 'Verificando dados na nuvem...', tok_pisin: 'Checkim data long klaud...', indonesian: 'Memverifikasi data di cloud...', - nepali: 'क्लाउडमा डाटा प्रमाणित गर्दै...' + nepali: 'क्लाउडमा डाटा प्रमाणित गर्दै...', + hindi: 'क्लाउड में डेटा सत्यापित कर रहे हैं...', + burmese: 'cloud တွင် ဒေတာကို အတည်ပြုနေသည်...', + thai: 'กำลังตรวจสอบข้อมูลในคลาวด์...', + mandarin: '正在验证云端数据...' }, pendingUploadsDetected: { english: 'Pending uploads detected', @@ -317,7 +495,11 @@ export const localizations = { brazilian_portuguese: 'Uploads pendentes detectados', tok_pisin: 'Painimaut sampela hap i no go yet', indonesian: 'Mendeteksi upload tertunda', - nepali: 'बाँकी अपलोडहरू पत्ता लाग्यो' + nepali: 'बाँकी अपलोडहरू पत्ता लाग्यो', + hindi: 'लंबित अपलोड का पता चला', + burmese: 'ဆိုင်းငံ့ထားသော အပ်လုဒ်များကို ရှာဖွေတွေ့ရှိသည်', + thai: 'ตรวจพบการอัปโหลดที่รอดำเนินการ', + mandarin: '检测到待上传' }, pendingUploadsMessage: { english: @@ -331,7 +513,13 @@ export const localizations = { indonesian: 'Harap tunggu semua perubahan terupload ke cloud sebelum melepas. Sambungkan ke internet dan tunggu sinkronisasi selesai.', nepali: - 'कृपया हटाउनु अघि सबै परिवर्तनहरू क्लाउडमा अपलोड हुनको लागि पर्खनुहोस्। इन्टरनेटमा जडान गर्नुहोस् र सिङ्क पूरा हुनको लागि पर्खनुहोस्।' + 'कृपया हटाउनु अघि सबै परिवर्तनहरू क्लाउडमा अपलोड हुनको लागि पर्खनुहोस्। इन्टरनेटमा जडान गर्नुहोस् र सिङ्क पूरा हुनको लागि पर्खनुहोस्।', + hindi: + 'कृपया अनलोड करने से पहले सभी परिवर्तनों के क्लाउड में अपलोड होने की प्रतीक्षा करें। इंटरनेट से कनेक्ट करें और सिंक पूरा होने की प्रतीक्षा करें।', + burmese: + 'ဖယ်ရှားမီ cloud သို့ အပြောင်းအလဲအားလုံး အပ်လုဒ်လုပ်ရန် စောင့်ပါ။ အင်တာနက်သို့ ချိတ်ဆက်ပြီး sync ပြီးမြောက်သည်အထိ စောင့်ပါ။', + thai: 'กรุณารอให้การเปลี่ยนแปลงทั้งหมดอัปโหลดไปยังคลาวด์ก่อนถอดออก เชื่อมต่ออินเทอร์เน็ตและรอให้การซิงค์เสร็จสมบูรณ์', + mandarin: '请在卸载前等待所有更改上传到云端。连接到互联网并等待同步完成。' }, readyToOffload: { english: 'Ready to offload', @@ -339,7 +527,11 @@ export const localizations = { brazilian_portuguese: 'Pronto para descarregar', tok_pisin: 'Redi long rausim', indonesian: 'Siap untuk melepas', - nepali: 'हटाउन तयार' + nepali: 'हटाउन तयार', + hindi: 'अनलोड करने के लिए तैयार', + burmese: 'ဖယ်ရှားရန် အဆင်သင့်', + thai: 'พร้อมถอดออก', + mandarin: '准备卸载' }, offloadWarning: { english: @@ -353,7 +545,13 @@ export const localizations = { indonesian: 'Ini akan menghapus salinan lokal. Data akan tetap aman di cloud dan dapat diunduh kembali nanti.', nepali: - 'यसले स्थानीय प्रतिलिपिहरू मेट्नेछ। डाटा क्लाउडमा सुरक्षित रहनेछ र पछि पुन: डाउनलोड गर्न सकिन्छ।' + 'यसले स्थानीय प्रतिलिपिहरू मेट्नेछ। डाटा क्लाउडमा सुरक्षित रहनेछ र पछि पुन: डाउनलोड गर्न सकिन्छ।', + hindi: + 'यह स्थानीय प्रतियां हटा देगा। डेटा क्लाउड में सुरक्षित रहेगा और बाद में फिर से डाउनलोड किया जा सकता है।', + burmese: + '၎င်းသည် ဒေသတွင်းမိတ္တူများကို ဖျက်ပါမည်။ ဒေတာသည် cloud တွင် ဘေးကင်းစွာ ရှိနေပြီး နောက်ပိုင်းတွင် ပြန်လည်ဒေါင်းလုဒ်လုပ်နိုင်သည်။', + thai: 'การดำเนินการนี้จะลบสำเนาท้องถิ่น ข้อมูลจะยังคงปลอดภัยในคลาวด์และสามารถดาวน์โหลดใหม่ได้ในภายหลัง', + mandarin: '这将删除本地副本。数据将安全地保留在云端,稍后可以重新下载。' }, storageToFree: { english: 'Storage to Free', @@ -361,7 +559,11 @@ export const localizations = { brazilian_portuguese: 'Armazenamento a Liberar', tok_pisin: 'Storage Long Freeup', indonesian: 'Penyimpanan yang Dibebaskan', - nepali: 'खाली गर्ने भण्डारण' + nepali: 'खाली गर्ने भण्डारण', + hindi: 'मुक्त करने के लिए भंडारण', + burmese: 'လွတ်လပ်စေရန် သိုလှောင်မှု', + thai: 'พื้นที่เก็บข้อมูลที่จะปล่อย', + mandarin: '要释放的存储空间' }, continue: { english: 'Continue', @@ -369,7 +571,11 @@ export const localizations = { brazilian_portuguese: 'Continuar', tok_pisin: 'Go Het', indonesian: 'Lanjutkan', - nepali: 'जारी राख्नुहोस्' + nepali: 'जारी राख्नुहोस्', + hindi: 'जारी रखें', + burmese: 'ဆက်လုပ်ပါ', + thai: 'ดำเนินการต่อ', + mandarin: '继续' }, continueToOffload: { english: 'Offload from Device', @@ -377,7 +583,11 @@ export const localizations = { brazilian_portuguese: 'Descarregar do Dispositivo', tok_pisin: 'Rausim long Mashin', indonesian: 'Lepas dari Perangkat', - nepali: 'उपकरणबाट हटाउनुहोस्' + nepali: 'उपकरणबाट हटाउनुहोस्', + hindi: 'डिवाइस से अनलोड करें', + burmese: 'စက်ပစ္စည်းမှ ဖယ်ရှားပါ', + thai: 'ถอดออกจากอุปกรณ์', + mandarin: '从设备卸载' }, offloadingQuest: { english: 'Offloading quest...', @@ -385,7 +595,11 @@ export const localizations = { brazilian_portuguese: 'Descarregando quest...', tok_pisin: 'Rausim quest...', indonesian: 'Melepas quest...', - nepali: 'क्वेस्ट हटाउँदै...' + nepali: 'क्वेस्ट हटाउँदै...', + hindi: 'क्वेस्ट अनलोड कर रहे हैं...', + burmese: 'Quest ကို ဖယ်ရှားနေသည်...', + thai: 'กำลังถอดเควสต์...', + mandarin: '正在卸载任务...' }, offloadComplete: { english: 'Quest offloaded successfully', @@ -393,7 +607,11 @@ export const localizations = { brazilian_portuguese: 'Quest descarregada com sucesso', tok_pisin: 'Quest i rausim orait', indonesian: 'Quest berhasil dilepas', - nepali: 'क्वेस्ट सफलतापूर्वक हटाइयो' + nepali: 'क्वेस्ट सफलतापूर्वक हटाइयो', + hindi: 'क्वेस्ट सफलतापूर्वक अनलोड हो गया', + burmese: 'Quest ကို အောင်မြင်စွာ ဖယ်ရှားပြီးပါပြီ', + thai: 'ถอดเควสต์สำเร็จ', + mandarin: '任务卸载成功' }, offloadError: { english: 'Failed to offload quest', @@ -401,7 +619,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao descarregar quest', tok_pisin: 'Pasin long rausim quest i no inap', indonesian: 'Gagal melepas quest', - nepali: 'क्वेस्ट हटाउन असफल' + nepali: 'क्वेस्ट हटाउन असफल', + hindi: 'क्वेस्ट अनलोड करने में विफल', + burmese: 'Quest ကို ဖယ်ရှားရန် မအောင်မြင်ပါ', + thai: 'ถอดเควสต์ไม่สำเร็จ', + mandarin: '卸载任务失败' }, cannotOffloadErrors: { english: 'Cannot offload - errors detected', @@ -409,7 +631,11 @@ export const localizations = { brazilian_portuguese: 'Não é possível descarregar - erros detectados', tok_pisin: 'No inap rausim - painimaut sampela rong', indonesian: 'Tidak dapat melepas - kesalahan terdeteksi', - nepali: 'हटाउन सकिँदैन - त्रुटिहरू पत्ता लाग्यो' + nepali: 'हटाउन सकिँदैन - त्रुटिहरू पत्ता लाग्यो', + hindi: 'अनलोड नहीं कर सकते - त्रुटियां पाई गईं', + burmese: 'ဖယ်ရှား၍မရပါ - အမှားများကို ရှာဖွေတွေ့ရှိသည်', + thai: 'ไม่สามารถถอดออกได้ - ตรวจพบข้อผิดพลาด', + mandarin: '无法卸载 - 检测到错误' }, allDataVerifiedInCloud: { english: 'All data verified in cloud', @@ -417,7 +643,11 @@ export const localizations = { brazilian_portuguese: 'Todos os dados verificados na nuvem', tok_pisin: 'Olgeta data i stret long klaud', indonesian: 'Semua data terverifikasi di cloud', - nepali: 'सबै डाटा क्लाउडमा प्रमाणित' + nepali: 'सबै डाटा क्लाउडमा प्रमाणित', + hindi: 'सभी डेटा क्लाउड में सत्यापित', + burmese: 'cloud တွင် ဒေတာအားလုံး အတည်ပြုပြီးပါပြီ', + thai: 'ข้อมูลทั้งหมดได้รับการยืนยันในคลาวด์', + mandarin: '所有数据已在云端验证' }, checkingPendingChanges: { english: 'Checking for pending changes...', @@ -425,7 +655,11 @@ export const localizations = { brazilian_portuguese: 'Verificando alterações pendentes...', tok_pisin: 'Checkim sampela senis i no go yet...', indonesian: 'Memeriksa perubahan tertunda...', - nepali: 'बाँकी परिवर्तनहरू जाँच गर्दै...' + nepali: 'बाँकी परिवर्तनहरू जाँच गर्दै...', + hindi: 'लंबित परिवर्तनों की जांच कर रहे हैं...', + burmese: 'ဆိုင်းငံ့ထားသော အပြောင်းအလဲများကို စစ်ဆေးနေသည်...', + thai: 'กำลังตรวจสอบการเปลี่ยนแปลงที่รอดำเนินการ...', + mandarin: '正在检查待处理的更改...' }, verifyingDatabaseRecords: { english: 'Verifying database records', @@ -433,7 +667,11 @@ export const localizations = { brazilian_portuguese: 'Verificando registros do banco de dados', tok_pisin: 'Checkim ol rekod long database', indonesian: 'Memverifikasi catatan database', - nepali: 'डाटाबेस रेकर्डहरू प्रमाणित गर्दै' + nepali: 'डाटाबेस रेकर्डहरू प्रमाणित गर्दै', + hindi: 'डेटाबेस रिकॉर्ड सत्यापित कर रहे हैं', + burmese: 'ဒေတာဘေ့စ်မှတ်တမ်းများကို အတည်ပြုနေသည်', + thai: 'กำลังตรวจสอบบันทึกฐานข้อมูล', + mandarin: '正在验证数据库记录' }, verifyingAttachments: { english: 'Verifying attachments', @@ -441,7 +679,11 @@ export const localizations = { brazilian_portuguese: 'Verificando anexos', tok_pisin: 'Checkim ol fail i pas long', indonesian: 'Memverifikasi lampiran', - nepali: 'संलग्नकहरू प्रमाणित गर्दै' + nepali: 'संलग्नकहरू प्रमाणित गर्दै', + hindi: 'संलग्नक सत्यापित कर रहे हैं', + burmese: 'ပူးတွဲဖိုင်များကို အတည်ပြုနေသည်', + thai: 'กำลังตรวจสอบไฟล์แนบ', + mandarin: '正在验证附件' }, waitingForUploads: { english: 'Waiting for Uploads', @@ -449,7 +691,11 @@ export const localizations = { brazilian_portuguese: 'Aguardando Uploads', tok_pisin: 'Wetim Upload', indonesian: 'Menunggu Upload', - nepali: 'अपलोडहरूको लागि पर्खँदै' + nepali: 'अपलोडहरूको लागि पर्खँदै', + hindi: 'अपलोड की प्रतीक्षा कर रहे हैं', + burmese: 'အပ်လုဒ်များအတွက် စောင့်ဆိုင်းနေသည်', + thai: 'กำลังรอการอัปโหลด', + mandarin: '等待上传' }, cannotOffload: { english: 'Cannot Offload', @@ -457,7 +703,11 @@ export const localizations = { brazilian_portuguese: 'Não é possível Descarregar', tok_pisin: 'No Inap Rausim', indonesian: 'Tidak dapat Melepas', - nepali: 'हटाउन सकिँदैन' + nepali: 'हटाउन सकिँदैन', + hindi: 'अनलोड नहीं कर सकते', + burmese: 'ဖယ်ရှား၍မရပါ', + thai: 'ไม่สามารถถอดออกได้', + mandarin: '无法卸载' }, analyzingRelatedRecords: { english: 'Analyzing related records...', @@ -465,7 +715,11 @@ export const localizations = { brazilian_portuguese: 'Analisando registros relacionados...', tok_pisin: 'Lukautim ol related records...', indonesian: 'Menganalisis catatan terkait...', - nepali: 'सम्बन्धित रेकर्डहरू विश्लेषण गर्दै...' + nepali: 'सम्बन्धित रेकर्डहरू विश्लेषण गर्दै...', + hindi: 'संबंधित रिकॉर्ड का विश्लेषण कर रहे हैं...', + burmese: 'ဆက်စပ်သော မှတ်တမ်းများကို ခွဲခြမ်းစိတ်ဖြာနေသည်...', + thai: 'กำลังวิเคราะห์บันทึกที่เกี่ยวข้อง...', + mandarin: '正在分析相关记录...' }, discoveryComplete: { english: 'Discovery complete', @@ -473,7 +727,11 @@ export const localizations = { brazilian_portuguese: 'Descoberta completa', tok_pisin: 'Discovery i pinis', indonesian: 'Penemuan selesai', - nepali: 'खोज पूरा भयो' + nepali: 'खोज पूरा भयो', + hindi: 'खोज पूर्ण', + burmese: 'ရှာဖွေမှု ပြီးမြောက်ပါပြီ', + thai: 'การค้นพบเสร็จสมบูรณ์', + mandarin: '发现完成' }, totalRecords: { english: 'Total Records', @@ -481,7 +739,11 @@ export const localizations = { brazilian_portuguese: 'Registros Totais', tok_pisin: 'Total Records', indonesian: 'Total Catatan', - nepali: 'कुल रेकर्डहरू' + nepali: 'कुल रेकर्डहरू', + hindi: 'कुल रिकॉर्ड', + burmese: 'စုစုပေါင်း မှတ်တမ်းများ', + thai: 'บันทึกทั้งหมด', + mandarin: '总记录数' }, discoveryErrorsOccurred: { english: @@ -495,7 +757,13 @@ export const localizations = { indonesian: 'Beberapa kesalahan terjadi selama penemuan. Anda masih dapat mengunduh catatan yang ditemukan.', nepali: - 'खोजको क्रममा केही त्रुटिहरू भए। तपाईं अझै पनि खोजिएका रेकर्डहरू डाउनलोड गर्न सक्नुहुन्छ।' + 'खोजको क्रममा केही त्रुटिहरू भए। तपाईं अझै पनि खोजिएका रेकर्डहरू डाउनलोड गर्न सक्नुहुन्छ।', + hindi: + 'खोज के दौरान कुछ त्रुटियां हुईं। आप अभी भी खोजे गए रिकॉर्ड डाउनलोड कर सकते हैं।', + burmese: + 'ရှာဖွေမှုအတွင်း အမှားအချို့ ဖြစ်ပွားခဲ့သည်။ သင်သည် ရှာဖွေတွေ့ရှိထားသော မှတ်တမ်းများကို ဆက်လက် ဒေါင်းလုဒ်လုပ်နိုင်ပါသည်။', + thai: 'เกิดข้อผิดพลาดบางอย่างระหว่างการค้นพบ คุณยังสามารถดาวน์โหลดบันทึกที่ค้นพบได้', + mandarin: '发现过程中发生了一些错误。您仍然可以下载已发现的记录。' }, questNotFoundInCloud: { english: @@ -509,7 +777,14 @@ export const localizations = { indonesian: 'Quest tidak ditemukan di basis data cloud. Mungkin hanya ada secara lokal atau Anda tidak memiliki izin untuk mengaksesnya. Silakan muat ulang halaman atau hubungi dukungan jika masalah ini tetap terjadi.', nepali: - 'क्लाउड डाटाबेसमा क्वेस्ट फेला परेन। यो स्थानीय रूपमा मात्र अवस्थित हुन सक्छ वा तपाईंसँग यसमा पहुँच गर्ने अनुमति नहुन सक्छ। पृष्ठ रिफ्रेश गर्नुहोस् वा यो समस्या जारी रहेमा समर्थनलाई सम्पर्क गर्नुहोस्।' + 'क्लाउड डाटाबेसमा क्वेस्ट फेला परेन। यो स्थानीय रूपमा मात्र अवस्थित हुन सक्छ वा तपाईंसँग यसमा पहुँच गर्ने अनुमति नहुन सक्छ। पृष्ठ रिफ्रेश गर्नुहोस् वा यो समस्या जारी रहेमा समर्थनलाई सम्पर्क गर्नुहोस्।', + hindi: + 'क्लाउड डेटाबेस में क्वेस्ट नहीं मिला। यह केवल स्थानीय रूप से मौजूद हो सकता है या आपके पास इसे एक्सेस करने की अनुमति नहीं हो सकती है। पृष्ठ को रीफ्रेश करने का प्रयास करें या यदि यह समस्या बनी रहती है तो सपोर्ट से संपर्क करें।', + burmese: + 'cloud ဒေတာဘေ့စ်တွင် Quest ကို မတွေ့ရှိပါ။ ၎င်းသည် ဒေသတွင်းတွင်သာ တည်ရှိနိုင်ပြီး သို့မဟုတ် သင်တွင် ၎င်းကို ဝင်ရောက်ခွင့် မရှိနိုင်ပါ။ စာမျက်နှာကို ပြန်လည်စတင်ရန် ကြိုးစားပါ သို့မဟုတ် ဤပြဿနာ ဆက်ရှိနေပါက အကူအညီကို ဆက်သွယ်ပါ။', + thai: 'ไม่พบเควสต์ในฐานข้อมูลคลาวด์ อาจมีอยู่เฉพาะในเครื่องหรือคุณอาจไม่มีสิทธิ์เข้าถึง ลองรีเฟรชหน้าเว็บหรือติดต่อฝ่ายสนับสนุนหากปัญหานี้ยังคงอยู่', + mandarin: + '在云端数据库中未找到任务。它可能仅存在于本地,或者您可能没有访问权限。请尝试刷新页面,如果问题持续存在,请联系支持。' }, discovering: { english: 'Discovering...', @@ -517,7 +792,11 @@ export const localizations = { brazilian_portuguese: 'Descobrindo...', tok_pisin: 'Painimaut...', indonesian: 'Menemukan...', - nepali: 'खोज्दै...' + nepali: 'खोज्दै...', + hindi: 'खोज रहे हैं...', + burmese: 'ရှာဖွေနေသည်...', + thai: 'กำลังค้นหา...', + mandarin: '正在发现...' }, continueToDownload: { english: 'Continue to Download', @@ -525,7 +804,11 @@ export const localizations = { brazilian_portuguese: 'Continuar para Download', tok_pisin: 'Go het long Download', indonesian: 'Lanjutkan ke Unduhan', - nepali: 'डाउनलोडमा जारी राख्नुहोस्' + nepali: 'डाउनलोडमा जारी राख्नुहोस्', + hindi: 'डाउनलोड जारी रखें', + burmese: 'ဒေါင်းလုဒ်လုပ်ရန် ဆက်လုပ်ပါ', + thai: 'ดำเนินการดาวน์โหลดต่อ', + mandarin: '继续下载' }, email: { english: 'Email', @@ -533,7 +816,11 @@ export const localizations = { brazilian_portuguese: 'E-mail', tok_pisin: 'Email', indonesian: 'Email', - nepali: 'इमेल' + nepali: 'इमेल', + hindi: 'ईमेल', + burmese: 'အီးမေးလ်', + thai: 'อีเมล', + mandarin: '电子邮件' }, emailAlreadyMemberMessage: { english: 'This email address is already a {role} of this project.', @@ -542,7 +829,11 @@ export const localizations = { brazilian_portuguese: 'Este endereço de e-mail já é {role} deste projeto.', tok_pisin: 'Dispela email adres i {role} pinis long dispela project.', indonesian: 'Alamat email ini sudah menjadi {role} dari proyek ini.', - nepali: 'यो इमेल ठेगाना पहिले नै यस प्रोजेक्टको {role} हो।' + nepali: 'यो इमेल ठेगाना पहिले नै यस प्रोजेक्टको {role} हो।', + hindi: 'यह ईमेल पता पहले से ही इस परियोजना का {role} है।', + burmese: 'ဤအီးမေးလ်လိပ်စာသည် ဤစီမံကိန်း၏ {role} ဖြစ်ပြီးသားဖြစ်သည်။', + thai: 'ที่อยู่อีเมลนี้เป็น {role} ของโครงการนี้อยู่แล้ว', + mandarin: '此电子邮件地址已经是此项目的 {role}。' }, emailRequired: { english: 'Email is required', @@ -550,7 +841,11 @@ export const localizations = { brazilian_portuguese: 'E-mail é obrigatório', tok_pisin: 'Email i mas', indonesian: 'Email diperlukan', - nepali: 'इमेल आवश्यक छ' + nepali: 'इमेल आवश्यक छ', + hindi: 'ईमेल आवश्यक है', + burmese: 'အီးမေးလ် လိုအပ်ပါသည်', + thai: 'ต้องใช้อีเมล', + mandarin: '需要电子邮件' }, nameRequired: { english: 'Name is required', @@ -558,7 +853,11 @@ export const localizations = { brazilian_portuguese: 'Nome é obrigatório', tok_pisin: 'Name i mas', indonesian: 'Nama diperlukan', - nepali: 'नाम आवश्यक छ' + nepali: 'नाम आवश्यक छ', + hindi: 'नाम आवश्यक है', + burmese: 'အမည် လိုအပ်ပါသည်', + thai: 'ต้องใช้ชื่อ', + mandarin: '需要姓名' }, descriptionTooLong: { english: 'Description must be less than {max} characters', @@ -566,7 +865,11 @@ export const localizations = { brazilian_portuguese: 'A descrição deve ter menos de {max} caracteres', tok_pisin: 'Description i no sem long {max} character', indonesian: 'Deskripsi harus kurang dari {max} karakter', - nepali: 'विवरण {max} अक्षरभन्दा कम हुनुपर्छ' + nepali: 'विवरण {max} अक्षरभन्दा कम हुनुपर्छ', + hindi: 'विवरण {max} अक्षरों से कम होना चाहिए', + burmese: 'ဖော်ပြချက်သည် {max} စာလုံးထက် နည်းရမည်', + thai: 'คำอธิบายต้องน้อยกว่า {max} ตัวอักษร', + mandarin: '描述必须少于 {max} 个字符' }, enterTranslation: { english: 'Enter your translation here', @@ -574,7 +877,11 @@ export const localizations = { brazilian_portuguese: 'Digite sua tradução aqui', tok_pisin: 'Putim translation bilong yu long hia', indonesian: 'Masukkan terjemahan Anda di sini', - nepali: 'यहाँ आफ्नो अनुवाद प्रविष्ट गर्नुहोस्' + nepali: 'यहाँ आफ्नो अनुवाद प्रविष्ट गर्नुहोस्', + hindi: 'यहाँ अपना अनुवाद दर्ज करें', + burmese: 'သင်၏ ဘာသာပြန်ဆိုချက်ကို ဤနေရာတွင် ထည့်သွင်းပါ', + thai: 'ป้อนคำแปลของคุณที่นี่', + mandarin: '在此输入您的翻译' }, enterTranscription: { english: 'Enter your transcription here', @@ -582,7 +889,11 @@ export const localizations = { brazilian_portuguese: 'Digite sua transcrição aqui', tok_pisin: 'Putim transcription bilong yu long hia', indonesian: 'Masukkan transkripsi Anda di sini', - nepali: 'यहाँ आफ्नो ट्रान्सक्रिप्सन प्रविष्ट गर्नुहोस्' + nepali: 'यहाँ आफ्नो ट्रान्सक्रिप्सन प्रविष्ट गर्नुहोस्', + hindi: 'यहाँ अपना ट्रांसक्रिप्शन दर्ज करें', + burmese: 'သင်၏ စာလုံးပေါင်းကို ဤနေရာတွင် ထည့်သွင်းပါ', + thai: 'ป้อนการถอดความของคุณที่นี่', + mandarin: '在此输入您的转录' }, enterYourTranscriptionIn: { english: 'Enter your transcription in {language}', @@ -590,7 +901,11 @@ export const localizations = { brazilian_portuguese: 'Digite sua transcrição em {language}', tok_pisin: 'Putim transcription bilong yu long {language}', indonesian: 'Masukkan transkripsi Anda dalam {language}', - nepali: '{language} मा आफ्नो ट्रान्सक्रिप्सन प्रविष्ट गर्नुहोस्' + nepali: '{language} मा आफ्नो ट्रान्सक्रिप्सन प्रविष्ट गर्नुहोस्', + hindi: '{language} में अपना ट्रांसक्रिप्शन दर्ज करें', + burmese: '{language} တွင် သင်၏ စာလုံးပေါင်းကို ထည့်သွင်းပါ', + thai: 'ป้อนการถอดความของคุณใน {language}', + mandarin: '用 {language} 输入您的转录' }, enterValidEmail: { english: 'Please enter a valid email', @@ -598,7 +913,11 @@ export const localizations = { brazilian_portuguese: 'Por favor, digite um e-mail válido', tok_pisin: 'Plis putim wanpela gutpela email', indonesian: 'Silakan masukkan email yang valid', - nepali: 'कृपया मान्य इमेल प्रविष्ट गर्नुहोस्' + nepali: 'कृपया मान्य इमेल प्रविष्ट गर्नुहोस्', + hindi: 'कृपया एक वैध ईमेल दर्ज करें', + burmese: 'ကျေးဇူးပြု၍ တရားဝင် အီးမေးလ်တစ်ခုကို ထည့်သွင်းပါ', + thai: 'กรุณาป้อนอีเมลที่ถูกต้อง', + mandarin: '请输入有效的电子邮件' }, enterYourEmail: { english: 'Enter your email', @@ -606,7 +925,11 @@ export const localizations = { brazilian_portuguese: 'Digite seu e-mail', tok_pisin: 'Putim email bilong yu', indonesian: 'Masukkan email Anda', - nepali: 'आफ्नो इमेल प्रविष्ट गर्नुहोस्' + nepali: 'आफ्नो इमेल प्रविष्ट गर्नुहोस्', + hindi: 'अपना ईमेल दर्ज करें', + burmese: 'သင်၏ အီးမေးလ်ကို ထည့်သွင်းပါ', + thai: 'ป้อนอีเมลของคุณ', + mandarin: '输入您的电子邮件' }, enterYourPassword: { english: 'Enter your password', @@ -614,7 +937,11 @@ export const localizations = { brazilian_portuguese: 'Digite sua senha', tok_pisin: 'Putim password bilong yu', indonesian: 'Masukkan kata sandi Anda', - nepali: 'आफ्नो पासवर्ड प्रविष्ट गर्नुहोस्' + nepali: 'आफ्नो पासवर्ड प्रविष्ट गर्नुहोस्', + hindi: 'अपना पासवर्ड दर्ज करें', + burmese: 'သင်၏ စကားဝှက်ကို ထည့်သွင်းပါ', + thai: 'ป้อนรหัสผ่านของคุณ', + mandarin: '输入您的密码' }, error: { english: 'Error', @@ -622,7 +949,11 @@ export const localizations = { brazilian_portuguese: 'Erro', tok_pisin: 'Rong', indonesian: 'Kesalahan', - nepali: 'त्रुटि' + nepali: 'त्रुटि', + hindi: 'त्रुटि', + burmese: 'အမှား', + thai: 'ข้อผิดพลาด', + mandarin: '错误' }, failedCreateTranslation: { english: 'Failed to create translation', @@ -630,7 +961,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao criar tradução', tok_pisin: 'I no inap mekim translation', indonesian: 'Gagal membuat terjemahan', - nepali: 'अनुवाद सिर्जना गर्न असफल' + nepali: 'अनुवाद सिर्जना गर्न असफल', + hindi: 'अनुवाद बनाने में विफल', + burmese: 'ဘာသာပြန်ဆိုချက်ကို ဖန်တီး၍မရပါ', + thai: 'สร้างคำแปลไม่สำเร็จ', + mandarin: '创建翻译失败' }, failedCreateTranscription: { english: 'Failed to create transcription', @@ -638,7 +973,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao criar transcrição', tok_pisin: 'I no inap mekim transcription', indonesian: 'Gagal membuat transkripsi', - nepali: 'ट्रान्सक्रिप्सन सिर्जना गर्न असफल' + nepali: 'ट्रान्सक्रिप्सन सिर्जना गर्न असफल', + hindi: 'ट्रांसक्रिप्शन बनाने में विफल', + burmese: 'စာလုံးပေါင်းကို ဖန်တီး၍မရပါ', + thai: 'สร้างการถอดความไม่สำเร็จ', + mandarin: '创建转录失败' }, failedLoadProjects: { english: 'Failed to load projects', @@ -646,7 +985,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao carregar projetos', tok_pisin: 'I no inap loadim ol project', indonesian: 'Gagal memuat proyek', - nepali: 'प्रोजेक्टहरू लोड गर्न असफल' + nepali: 'प्रोजेक्टहरू लोड गर्न असफल', + hindi: 'परियोजनाएं लोड करने में विफल', + burmese: 'စီမံကိန်းများကို လုပ်ဆောင်၍မရပါ', + thai: 'โหลดโครงการไม่สำเร็จ', + mandarin: '加载项目失败' }, failedLoadQuests: { english: 'Failed to load quests', @@ -654,7 +997,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao carregar missões', tok_pisin: 'I no inap loadim ol quest', indonesian: 'Gagal memuat misi', - nepali: 'क्वेस्टहरू लोड गर्न असफल' + nepali: 'क्वेस्टहरू लोड गर्न असफल', + hindi: 'क्वेस्ट लोड करने में विफल', + burmese: 'Quest များကို လုပ်ဆောင်၍မရပါ', + thai: 'โหลดเควสต์ไม่สำเร็จ', + mandarin: '加载任务失败' }, failedResetPassword: { english: 'Failed to reset password', @@ -662,7 +1009,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao redefinir senha', tok_pisin: 'I no inap resetim password', indonesian: 'Gagal mereset kata sandi', - nepali: 'पासवर्ड रिसेट गर्न असफल' + nepali: 'पासवर्ड रिसेट गर्न असफल', + hindi: 'पासवर्ड रीसेट करने में विफल', + burmese: 'စကားဝှက်ကို ပြန်လည်သတ်မှတ်၍မရပါ', + thai: 'รีเซ็ตรหัสผ่านไม่สำเร็จ', + mandarin: '重置密码失败' }, failedSendResetEmail: { english: 'Failed to send reset email', @@ -670,7 +1021,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao enviar e-mail de redefinição', tok_pisin: 'I no inap salim reset email', indonesian: 'Gagal mengirim email reset', - nepali: 'रिसेट इमेल पठाउन असफल' + nepali: 'रिसेट इमेल पठाउन असफल', + hindi: 'रीसेट ईमेल भेजने में विफल', + burmese: 'ပြန်လည်သတ်မှတ်ရန် အီးမေးလ်ပို့၍မရပါ', + thai: 'ส่งอีเมลรีเซ็ตไม่สำเร็จ', + mandarin: '发送重置电子邮件失败' }, failedToAcceptInvitation: { english: 'Failed to accept invitation. Please try again.', @@ -679,7 +1034,11 @@ export const localizations = { 'Falha ao aceitar o convite. Por favor, tente novamente.', tok_pisin: 'I no inap akseptim invitation. Plis traim gen.', indonesian: 'Gagal menerima undangan. Silakan coba lagi.', - nepali: 'आमन्त्रण स्वीकार गर्न असफल। कृपया फेरि प्रयास गर्नुहोस्।' + nepali: 'आमन्त्रण स्वीकार गर्न असफल। कृपया फेरि प्रयास गर्नुहोस्।', + hindi: 'आमंत्रण स्वीकार करने में विफल। कृपया पुनः प्रयास करें।', + burmese: 'ဖိတ်ခေါ်မှုကို လက်ခံ၍မရပါ။ ကျေးဇူးပြု၍ ထပ်မံကြိုးစားပါ။', + thai: 'ยอมรับคำเชิญไม่สำเร็จ กรุณาลองอีกครั้ง', + mandarin: '接受邀请失败。请重试。' }, failedToDeclineInvitation: { english: 'Failed to decline invitation. Please try again.', @@ -688,7 +1047,23 @@ export const localizations = { 'Falha ao recusar o convite. Por favor, tente novamente.', tok_pisin: 'I no inap refusim invitation. Plis traim gen.', indonesian: 'Gagal menolak undangan. Silakan coba lagi.', - nepali: 'आमन्त्रण अस्वीकार गर्न असफल। कृपया फेरि प्रयास गर्नुहोस्।' + nepali: 'आमन्त्रण अस्वीकार गर्न असफल। कृपया फेरि प्रयास गर्नुहोस्।', + hindi: 'आमंत्रण अस्वीकार करने में विफल। कृपया पुनः प्रयास करें।', + burmese: 'ဖိတ်ခေါ်မှုကို ငြင်းဆို၍မရပါ။ ကျေးဇူးပြု၍ ထပ်မံကြိုးစားပါ။', + thai: 'ปฏิเสธคำเชิญไม่สำเร็จ กรุณาลองอีกครั้ง', + mandarin: '拒绝邀请失败。请重试。' + }, + mustBeOnlineToAcceptInvite: { + english: 'You must be online to accept an invitation', + spanish: 'Debes estar en línea para aceptar una invitación', + brazilian_portuguese: 'Você precisa estar online para aceitar um convite', + tok_pisin: 'Yu mas stap long internet bilong accept invitation', + indonesian: 'Anda harus online untuk menerima undangan', + nepali: 'आमन्त्रण स्वीकार गर्न तपाईं अनलाइनमा हुनुपर्छ', + hindi: 'आमंत्रण स्वीकार करने के लिए आपको ऑनलाइन होना होगा', + burmese: 'ဖိတ်ခေါ်မှုကို လက်ခံရန် သင်သည် အင်တာနက်ရှိရမည်', + thai: 'คุณต้องออนไลน์เพื่อยอมรับคำเชิญ', + mandarin: '您必须在线才能接受邀请' }, failedToVote: { english: 'Failed to submit vote', @@ -696,7 +1071,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao enviar voto', tok_pisin: 'I no inap salim vote', indonesian: 'Gagal mengirim suara', - nepali: 'मत पेश गर्न असफल' + nepali: 'मत पेश गर्न असफल', + hindi: 'मत जमा करने में विफल', + burmese: 'မဲပေးရန် မအောင်မြင်ပါ', + thai: 'ส่งคะแนนไม่สำเร็จ', + mandarin: '提交投票失败' }, fillFields: { english: 'Please fill in all required fields', @@ -704,7 +1083,11 @@ export const localizations = { brazilian_portuguese: 'Por favor, preencha todos os campos obrigatórios', tok_pisin: 'Plis fulupim olgeta field i mas', indonesian: 'Silakan isi semua bidang yang diperlukan', - nepali: 'कृपया सबै आवश्यक फिल्डहरू भर्नुहोस्' + nepali: 'कृपया सबै आवश्यक फिल्डहरू भर्नुहोस्', + hindi: 'कृपया सभी आवश्यक फ़ील्ड भरें', + burmese: 'ကျေးဇူးပြု၍ လိုအပ်သော အကွက်အားလုံးကို ဖြည့်ပါ', + thai: 'กรุณากรอกข้อมูลในช่องที่จำเป็นทั้งหมด', + mandarin: '请填写所有必填字段' }, forgotPassword: { english: 'I forgot my password', @@ -712,7 +1095,11 @@ export const localizations = { brazilian_portuguese: 'Esqueci minha senha', tok_pisin: 'Mi lusim password bilong mi', indonesian: 'Saya lupa kata sandi saya', - nepali: 'मैले पासवर्ड बिर्सेँ' + nepali: 'मैले पासवर्ड बिर्सेँ', + hindi: 'मैंने अपना पासवर्ड भूल गया', + burmese: 'ကျွန်ုပ်၏ စကားဝှက်ကို မေ့သွားပါသည်', + thai: 'ฉันลืมรหัสผ่าน', + mandarin: '我忘记了密码' }, invalidResetLink: { english: 'Invalid or expired reset link', @@ -720,7 +1107,12 @@ export const localizations = { brazilian_portuguese: 'Link de redefinição inválido ou expirado', tok_pisin: 'Reset link i no gutpela o i pinis', indonesian: 'Tautan reset tidak valid atau kedaluwarsa', - nepali: 'अमान्य वा समाप्त रिसेट लिंक' + nepali: 'अमान्य वा समाप्त रिसेट लिंक', + hindi: 'अमान्य या समाप्त रीसेट लिंक', + burmese: + 'မမှန်ကန်သော သို့မဟုတ် သက်တမ်းကုန်ဆုံးသော ပြန်လည်သတ်မှတ်ရန် လင့်ခ်', + thai: 'ลิงก์รีเซ็ตไม่ถูกต้องหรือหมดอายุ', + mandarin: '无效或已过期的重置链接' }, logInToTranslate: { english: 'You must be logged in to submit translations', @@ -728,7 +1120,11 @@ export const localizations = { brazilian_portuguese: 'Você precisa estar logado para enviar traduções', tok_pisin: 'Yu mas login pastaim long salim ol translation', indonesian: 'Anda harus masuk untuk mengirim terjemahan', - nepali: 'अनुवादहरू पेश गर्न तपाईं लग इन हुनुपर्छ' + nepali: 'अनुवादहरू पेश गर्न तपाईं लग इन हुनुपर्छ', + hindi: 'अनुवाद जमा करने के लिए आपको लॉग इन होना होगा', + burmese: 'ဘာသာပြန်ဆိုချက်များ တင်ရန် သင်သည် အကောင့်ဝင်ရမည်', + thai: 'คุณต้องเข้าสู่ระบบเพื่อส่งคำแปล', + mandarin: '您必须登录才能提交翻译' }, logInToVote: { english: 'You must be logged in to vote', @@ -736,7 +1132,11 @@ export const localizations = { brazilian_portuguese: 'Você precisa estar logado para votar', tok_pisin: 'Yu mas login pastaim long vote', indonesian: 'Anda harus masuk untuk memberikan suara', - nepali: 'मत दिन तपाईं लग इन हुनुपर्छ' + nepali: 'मत दिन तपाईं लग इन हुनुपर्छ', + hindi: 'मत देने के लिए आपको लॉग इन होना होगा', + burmese: 'မဲပေးရန် သင်သည် အကောင့်ဝင်ရမည်', + thai: 'คุณต้องเข้าสู่ระบบเพื่อลงคะแนน', + mandarin: '您必须登录才能投票' }, menu: { english: 'Menu', @@ -744,7 +1144,11 @@ export const localizations = { brazilian_portuguese: 'Menu', tok_pisin: 'Menu', indonesian: 'Menu', - nepali: 'मेनु' + nepali: 'मेनु', + hindi: 'मेनू', + burmese: 'မီနူး', + thai: 'เมนู', + mandarin: '菜单' }, newTranslation: { english: 'New Translation', @@ -752,7 +1156,11 @@ export const localizations = { brazilian_portuguese: 'Nova Tradução', tok_pisin: 'Nupela Translation', indonesian: 'Terjemahan Baru', - nepali: 'नयाँ अनुवाद' + nepali: 'नयाँ अनुवाद', + hindi: 'नया अनुवाद', + burmese: 'ဘာသာပြန်ဆိုချက် အသစ်', + thai: 'คำแปลใหม่', + mandarin: '新翻译' }, newTranscription: { english: 'New Transcription', @@ -760,7 +1168,11 @@ export const localizations = { brazilian_portuguese: 'Nova Transcrição', tok_pisin: 'Nupela Transcription', indonesian: 'Transkripsi Baru', - nepali: 'नयाँ ट्रान्सक्रिप्सन' + nepali: 'नयाँ ट्रान्सक्रिप्सन', + hindi: 'नया ट्रांसक्रिप्शन', + burmese: 'စာလုံးပေါင်း အသစ်', + thai: 'การถอดความใหม่', + mandarin: '新转录' }, newUser: { english: 'New user?', @@ -768,7 +1180,11 @@ export const localizations = { brazilian_portuguese: 'Novo usuário?', tok_pisin: 'Nupela user?', indonesian: 'Pengguna baru?', - nepali: 'नयाँ प्रयोगकर्ता?' + nepali: 'नयाँ प्रयोगकर्ता?', + hindi: 'नया उपयोगकर्ता?', + burmese: 'အသုံးပြုသူ အသစ်လား?', + thai: 'ผู้ใช้ใหม่?', + mandarin: '新用户?' }, newUserRegistration: { english: 'New User Registration', @@ -776,7 +1192,11 @@ export const localizations = { brazilian_portuguese: 'Registro de Novo Usuário', tok_pisin: 'Nupela User Registration', indonesian: 'Pendaftaran Pengguna Baru', - nepali: 'नयाँ प्रयोगकर्ता दर्ता' + nepali: 'नयाँ प्रयोगकर्ता दर्ता', + hindi: 'नया उपयोगकर्ता पंजीकरण', + burmese: 'အသုံးပြုသူ အသစ် မှတ်ပုံတင်ခြင်း', + thai: 'การลงทะเบียนผู้ใช้ใหม่', + mandarin: '新用户注册' }, noComment: { english: 'No Comment', @@ -784,7 +1204,11 @@ export const localizations = { brazilian_portuguese: 'Sem Comentários', tok_pisin: 'No gat comment', indonesian: 'Tidak Ada Komentar', - nepali: 'कुनै टिप्पणी छैन' + nepali: 'कुनै टिप्पणी छैन', + hindi: 'कोई टिप्पणी नहीं', + burmese: 'မှတ်ချက်မရှိပါ', + thai: 'ไม่มีความคิดเห็น', + mandarin: '无评论' }, noProject: { english: 'No active project found', @@ -792,7 +1216,11 @@ export const localizations = { brazilian_portuguese: 'Nenhum projeto ativo encontrado', tok_pisin: 'No gat active project', indonesian: 'Tidak ada proyek aktif yang ditemukan', - nepali: 'कुनै सक्रिय प्रोजेक्ट फेला परेन' + nepali: 'कुनै सक्रिय प्रोजेक्ट फेला परेन', + hindi: 'कोई सक्रिय परियोजना नहीं मिली', + burmese: 'အသက်ဝင်သော စီမံကိန်း မတွေ့ရှိပါ', + thai: 'ไม่พบโครงการที่ใช้งานอยู่', + mandarin: '未找到活动项目' }, ok: { english: 'OK', @@ -800,7 +1228,11 @@ export const localizations = { brazilian_portuguese: 'OK', tok_pisin: 'Orait', indonesian: 'OK', - nepali: 'ठीक छ' + nepali: 'ठीक छ', + hindi: 'ठीक है', + burmese: 'အိုကေ', + thai: 'ตกลง', + mandarin: '确定' }, offline: { english: 'Offline', @@ -808,7 +1240,11 @@ export const localizations = { brazilian_portuguese: 'Offline', tok_pisin: 'No gat internet', indonesian: 'Offline', - nepali: 'अफलाइन' + nepali: 'अफलाइन', + hindi: 'ऑफलाइन', + burmese: 'အင်တာနက်မရှိ', + thai: 'ออฟไลน์', + mandarin: '离线' }, password: { english: 'Password', @@ -816,7 +1252,11 @@ export const localizations = { brazilian_portuguese: 'Senha', tok_pisin: 'Password', indonesian: 'Kata Sandi', - nepali: 'पासवर्ड' + nepali: 'पासवर्ड', + hindi: 'पासवर्ड', + burmese: 'စကားဝှက်', + thai: 'รหัสผ่าน', + mandarin: '密码' }, passwordRequired: { english: 'Password is required', @@ -824,7 +1264,11 @@ export const localizations = { brazilian_portuguese: 'Senha é obrigatória', tok_pisin: 'Password i mas', indonesian: 'Kata sandi diperlukan', - nepali: 'पासवर्ड आवश्यक छ' + nepali: 'पासवर्ड आवश्यक छ', + hindi: 'पासवर्ड आवश्यक है', + burmese: 'စကားဝှက် လိုအပ်ပါသည်', + thai: 'ต้องใช้รหัสผ่าน', + mandarin: '需要密码' }, passwordMinLength: { english: 'Password must be at least 6 characters', @@ -832,7 +1276,11 @@ export const localizations = { brazilian_portuguese: 'A senha deve ter pelo menos 6 caracteres', tok_pisin: 'Password i mas gat 6 character', indonesian: 'Kata sandi harus minimal 6 karakter', - nepali: 'पासवर्ड कम्तिमा ६ अक्षर हुनुपर्छ' + nepali: 'पासवर्ड कम्तिमा ६ अक्षर हुनुपर्छ', + hindi: 'पासवर्ड कम से कम 6 अक्षर का होना चाहिए', + burmese: 'စကားဝှက်သည် အနည်းဆုံး ၆ စာလုံး ရှိရမည်', + thai: 'รหัสผ่านต้องมีอย่างน้อย 6 ตัวอักษร', + mandarin: '密码必须至少 6 个字符' }, passwordsNoMatch: { english: 'Passwords do not match', @@ -840,7 +1288,11 @@ export const localizations = { brazilian_portuguese: 'As senhas não coincidem', tok_pisin: 'Ol password i no sem', indonesian: 'Kata sandi tidak cocok', - nepali: 'पासवर्डहरू मेल खाँदैनन्' + nepali: 'पासवर्डहरू मेल खाँदैनन्', + hindi: 'पासवर्ड मेल नहीं खाते', + burmese: 'စကားဝှက်များ ကိုက်ညီမှုမရှိပါ', + thai: 'รหัสผ่านไม่ตรงกัน', + mandarin: '密码不匹配' }, passwordResetSuccess: { english: 'Password has been reset successfully', @@ -848,7 +1300,11 @@ export const localizations = { brazilian_portuguese: 'A senha foi redefinida com sucesso', tok_pisin: 'Password i reset gut pinis', indonesian: 'Kata sandi berhasil direset', - nepali: 'पासवर्ड सफलतापूर्वक रिसेट गरियो' + nepali: 'पासवर्ड सफलतापूर्वक रिसेट गरियो', + hindi: 'पासवर्ड सफलतापूर्वक रीसेट हो गया', + burmese: 'စကားဝှက်ကို အောင်မြင်စွာ ပြန်လည်သတ်မှတ်ပြီးပါပြီ', + thai: 'รีเซ็ตรหัสผ่านสำเร็จ', + mandarin: '密码已成功重置' }, projectDownloadFailed: { english: @@ -862,7 +1318,13 @@ export const localizations = { indonesian: 'Undangan diterima, tetapi unduhan proyek gagal. Anda dapat mengunduhnya nanti dari halaman proyek.', nepali: - 'आमन्त्रण स्वीकार गरियो, तर प्रोजेक्ट डाउनलोड असफल भयो। तपाईं पछि प्रोजेक्ट पृष्ठबाट डाउनलोड गर्न सक्नुहुन्छ।' + 'आमन्त्रण स्वीकार गरियो, तर प्रोजेक्ट डाउनलोड असफल भयो। तपाईं पछि प्रोजेक्ट पृष्ठबाट डाउनलोड गर्न सक्नुहुन्छ।', + hindi: + 'आमंत्रण स्वीकार कर लिया गया, लेकिन परियोजना डाउनलोड विफल रहा। आप बाद में परियोजनाएं पृष्ठ से इसे डाउनलोड कर सकते हैं।', + burmese: + 'ဖိတ်ခေါ်မှုကို လက်ခံပြီးပါပြီ၊ သို့သော် စီမံကိန်း ဒေါင်းလုဒ်လုပ်ရန် မအောင်မြင်ပါ။ သင်သည် နောက်ပိုင်းတွင် စီမံကိန်းများ စာမျက်နှာမှ ဒေါင်းလုဒ်လုပ်နိုင်သည်။', + thai: 'ยอมรับคำเชิญแล้ว แต่ดาวน์โหลดโครงการไม่สำเร็จ คุณสามารถดาวน์โหลดได้ในภายหลังจากหน้าความโครงการ', + mandarin: '邀请已接受,但项目下载失败。您可以稍后从项目页面下载它。' }, projects: { english: 'Projects', @@ -870,7 +1332,11 @@ export const localizations = { brazilian_portuguese: 'Projetos', tok_pisin: 'Ol Project', indonesian: 'Proyek', - nepali: 'प्रोजेक्टहरू' + nepali: 'प्रोजेक्टहरू', + hindi: 'परियोजनाएं', + burmese: 'စီမံကိန်းများ', + thai: 'โครงการ', + mandarin: '项目' }, quests: { english: 'Quests', @@ -878,13 +1344,23 @@ export const localizations = { brazilian_portuguese: 'Missões', tok_pisin: 'Ol Quest', indonesian: 'Misi', - nepali: 'क्वेस्टहरू' + nepali: 'क्वेस्टहरू', + hindi: 'क्वेस्ट', + burmese: 'Quest များ', + thai: 'เควสต์', + mandarin: '任务' }, project: { english: 'Project', spanish: 'Proyecto', brazilian_portuguese: 'Projeto', - nepali: 'प्रोजेक्ट' + tok_pisin: 'Projek', + indonesian: 'Proyek', + nepali: 'प्रोजेक्ट', + hindi: 'परियोजना', + burmese: 'စီမံကိန်း', + thai: 'โครงการ', + mandarin: '项目' }, noProjectsFound: { english: 'No projects found', @@ -892,7 +1368,11 @@ export const localizations = { brazilian_portuguese: 'Nenhum projeto encontrado', tok_pisin: 'Nogat projek i painim', indonesian: 'Tidak ada proyek yang ditemukan', - nepali: 'कुनै प्रोजेक्ट फेला परेन' + nepali: 'कुनै प्रोजेक्ट फेला परेन', + hindi: 'कोई परियोजना नहीं मिली', + burmese: 'စီမံကိန်း မတွေ့ရှိပါ', + thai: 'ไม่พบโครงการ', + mandarin: '未找到项目' }, noProjectsYet: { english: 'No projects yet', @@ -900,7 +1380,11 @@ export const localizations = { brazilian_portuguese: 'Ainda não há projetos', tok_pisin: 'I no gat projek yet', indonesian: 'Belum ada proyek', - nepali: 'अहिलेसम्म कुनै प्रोजेक्ट छैन' + nepali: 'अहिलेसम्म कुनै प्रोजेक्ट छैन', + hindi: 'अभी तक कोई परियोजना नहीं', + burmese: 'အခုထိ စီမံကိန်း မရှိသေးပါ', + thai: 'ยังไม่มีโครงการ', + mandarin: '还没有项目' }, noProjectsAvailable: { english: 'No projects available', @@ -908,7 +1392,11 @@ export const localizations = { brazilian_portuguese: 'Nenhum projeto disponível', tok_pisin: 'Nogat projek i stap', indonesian: 'Tidak ada proyek yang tersedia', - nepali: 'कुनै प्रोजेक्ट उपलब्ध छैन' + nepali: 'कुनै प्रोजेक्ट उपलब्ध छैन', + hindi: 'कोई परियोजना उपलब्ध नहीं', + burmese: 'ရရှိနိုင်သော စီမံကိန်း မရှိပါ', + thai: 'ไม่มีโครงการที่พร้อมใช้งาน', + mandarin: '没有可用的项目' }, createProject: { english: 'Create Project', @@ -916,7 +1404,11 @@ export const localizations = { brazilian_portuguese: 'Criar projeto', tok_pisin: 'Wokim Nupela Projek', indonesian: 'Buat Proyek', - nepali: 'प्रोजेक्ट सिर्जना गर्नुहोस्' + nepali: 'प्रोजेक्ट सिर्जना गर्नुहोस्', + hindi: 'परियोजना बनाएं', + burmese: 'စီမံကိန်း ဖန်တီးပါ', + thai: 'สร้างโครงการ', + mandarin: '创建项目' }, published: { english: 'Published', @@ -924,7 +1416,11 @@ export const localizations = { brazilian_portuguese: 'Publicado', tok_pisin: 'Publisim pinis', indonesian: 'Diterbitkan', - nepali: 'प्रकाशित' + nepali: 'प्रकाशित', + hindi: 'प्रकाशित', + burmese: 'ထုတ်ဝေပြီး', + thai: 'เผยแพร่แล้ว', + mandarin: '已发布' }, cannotPublishWhileOffline: { english: 'Cannot save to cloud while offline', @@ -933,7 +1429,11 @@ export const localizations = { 'Não é possível salvar na nuvem enquanto está desconectado', tok_pisin: 'I no inap save long cloud long no gat internet', indonesian: 'Tidak dapat menyimpan ke cloud saat offline', - nepali: 'अफलाइन हुँदा क्लाउडमा सेभ गर्न सकिँदैन' + nepali: 'अफलाइन हुँदा क्लाउडमा सेभ गर्न सकिँदैन', + hindi: 'ऑफलाइन होने पर क्लाउड में सहेज नहीं सकते', + burmese: 'အင်တာနက်မရှိသောအချိန်တွင် cloud သို့ သိမ်းဆည်း၍မရပါ', + thai: 'ไม่สามารถบันทึกลงคลาวด์ได้ขณะออฟไลน์', + mandarin: '离线时无法保存到云端' }, chapters: { english: 'Chapters', @@ -941,7 +1441,11 @@ export const localizations = { brazilian_portuguese: 'Capítulos', tok_pisin: 'Chapter', indonesian: 'Bab', - nepali: 'अध्यायहरू' + nepali: 'अध्यायहरू', + hindi: 'अध्याय', + burmese: 'အခန်းများ', + thai: 'บท', + mandarin: '章节' }, chapter: { english: 'Chapter', @@ -949,7 +1453,11 @@ export const localizations = { brazilian_portuguese: 'Capítulo', tok_pisin: 'Chapter', indonesian: 'Bab', - nepali: 'अध्याय' + nepali: 'अध्याय', + hindi: 'अध्याय', + burmese: 'အခန်း', + thai: 'บท', + mandarin: '章节' }, publishChapter: { english: 'Save to Cloud', @@ -957,7 +1465,11 @@ export const localizations = { brazilian_portuguese: 'Salvar na Nuvem', tok_pisin: 'Save long Cloud', indonesian: 'Simpan ke Cloud', - nepali: 'क्लाउडमा सेभ गर्नुहोस्' + nepali: 'क्लाउडमा सेभ गर्नुहोस्', + hindi: 'क्लाउड में सहेजें', + burmese: 'Cloud သို့ သိမ်းဆည်းပါ', + thai: 'บันทึกลงคลาวด์', + mandarin: '保存到云端' }, publish: { english: 'Save', @@ -965,7 +1477,11 @@ export const localizations = { nepali: 'सेभ गर्नुहोस्', brazilian_portuguese: 'Salvar', tok_pisin: 'Save', - indonesian: 'Simpan' + indonesian: 'Simpan', + hindi: 'सहेजें', + burmese: 'သိမ်းဆည်းပါ', + thai: 'บันทึก', + mandarin: '保存' }, publishChapterMessage: { english: @@ -979,13 +1495,26 @@ export const localizations = { indonesian: 'Ini akan membuat salinan permanen dari {questName} di cloud.\n\nSemua rekaman akan disimpan sebagai snapshot yang tidak dapat diubah. Setelah disimpan, versi ini tidak dapat diubah, tetapi Anda dapat membuat versi baru nanti jika diperlukan.\n\nJika buku atau proyek induk belum disimpan ke cloud, mereka akan disimpan secara otomatis.', nepali: - 'यसले {questName} को स्थायी प्रतिलिपि क्लाउडमा सिर्जना गर्नेछ।\n\nसबै रेकर्डिङहरू अपरिवर्तनीय स्न्यापशटको रूपमा सेभ हुनेछन्। एक पटक सेभ गरेपछि, यो संस्करण परिवर्तन गर्न सकिँदैन, तर आवश्यक भएमा तपाईं पछि नयाँ संस्करणहरू सिर्जना गर्न सक्नुहुन्छ।\n\nयदि अभिभावक पुस्तक वा प्रोजेक्ट अझै क्लाउडमा सेभ गरिएको छैन भने, तिनीहरू स्वचालित रूपमा सेभ हुनेछन्।' + 'यसले {questName} को स्थायी प्रतिलिपि क्लाउडमा सिर्जना गर्नेछ।\n\nसबै रेकर्डिङहरू अपरिवर्तनीय स्न्यापशटको रूपमा सेभ हुनेछन्। एक पटक सेभ गरेपछि, यो संस्करण परिवर्तन गर्न सकिँदैन, तर आवश्यक भएमा तपाईं पछि नयाँ संस्करणहरू सिर्जना गर्न सक्नुहुन्छ।\n\nयदि अभिभावक पुस्तक वा प्रोजेक्ट अझै क्लाउडमा सेभ गरिएको छैन भने, तिनीहरू स्वचालित रूपमा सेभ हुनेछन्।', + hindi: + 'यह क्लाउड में {questName} की एक स्थायी प्रतिलिपि बनाएगा।\n\nसभी रिकॉर्डिंग एक अपरिवर्तनीय स्नैपशॉट के रूप में सहेजी जाएंगी। एक बार सहेजने के बाद, इस संस्करण को बदला नहीं जा सकता, लेकिन आवश्यकता होने पर आप बाद में नए संस्करण बना सकते हैं।\n\nयदि मूल पुस्तक या परियोजना अभी तक क्लाउड में सहेजी नहीं गई है, तो वे स्वचालित रूप से सहेजी जाएंगी।', + burmese: + '၎င်းသည် cloud တွင် {questName} ၏ အမြဲတမ်း မိတ္တူတစ်ခုကို ဖန်တီးပါမည်။\n\nမှတ်တမ်းအားလုံးကို ပြောင်းလဲ၍မရသော snapshot အဖြစ် သိမ်းဆည်းပါမည်။ တစ်ကြိမ် သိမ်းဆည်းပြီးပါက၊ ဤဗားရှင်းကို ပြောင်းလဲ၍မရပါ၊ သို့သော် လိုအပ်ပါက နောက်ပိုင်းတွင် ဗားရှင်းအသစ်များ ဖန်တီးနိုင်ပါသည်။\n\nမိဘစာအုပ် သို့မဟုတ် စီမံကိန်းကို cloud တွင် မသိမ်းဆည်းရသေးပါက၊ ၎င်းတို့ကို အလိုအလျောက် သိမ်းဆည်းပါမည်။', + thai: 'การดำเนินการนี้จะสร้างสำเนาถาวรของ {questName} ในคลาวด์\n\nการบันทึกทั้งหมดจะถูกบันทึกเป็นภาพถ่ายที่ไม่สามารถเปลี่ยนแปลงได้ เมื่อบันทึกแล้ว เวอร์ชันนี้ไม่สามารถเปลี่ยนแปลงได้ แต่คุณสามารถสร้างเวอร์ชันใหม่ในภายหลังหากจำเป็น\n\nหากหนังสือหรือโครงการหลักยังไม่ได้บันทึกลงคลาวด์ จะถูกบันทึกโดยอัตโนมัติ', + mandarin: + '这将在云端创建 {questName} 的永久副本。\n\n所有录音将保存为不可变的快照。保存后,此版本无法更改,但如果需要,您可以稍后创建新版本。\n\n如果父书籍或项目尚未保存到云端,它们将自动保存。' }, quest: { english: 'Quest', spanish: 'Misión', brazilian_portuguese: 'Missão', - nepali: 'क्वेस्ट' + tok_pisin: 'Quest', + indonesian: 'Misi', + nepali: 'क्वेस्ट', + hindi: 'क्वेस्ट', + burmese: 'Quest', + thai: 'เควสต์', + mandarin: '任务' }, questOptions: { english: 'Quest Options', @@ -993,7 +1522,11 @@ export const localizations = { brazilian_portuguese: 'Opções de Missão', tok_pisin: 'Quest Options', indonesian: 'Opsi Misi', - nepali: 'क्वेस्ट विकल्पहरू' + nepali: 'क्वेस्ट विकल्पहरू', + hindi: 'क्वेस्ट विकल्प', + burmese: 'Quest ရွေးချယ်စရာများ', + thai: 'ตัวเลือกเควสต์', + mandarin: '任务选项' }, recording: { english: 'Recording', @@ -1001,7 +1534,11 @@ export const localizations = { brazilian_portuguese: 'Gravando', tok_pisin: 'Recording', indonesian: 'Merekam', - nepali: 'रेकर्डिङ' + nepali: 'रेकर्डिङ', + hindi: 'रिकॉर्डिंग', + burmese: 'မှတ်တမ်းတင်နေသည်', + thai: 'กำลังบันทึก', + mandarin: '正在录制' }, register: { english: 'Register', @@ -1009,7 +1546,11 @@ export const localizations = { brazilian_portuguese: 'Registrar', tok_pisin: 'Register', indonesian: 'Daftar', - nepali: 'दर्ता गर्नुहोस्' + nepali: 'दर्ता गर्नुहोस्', + hindi: 'पंजीकरण करें', + burmese: 'မှတ်ပုံတင်ပါ', + thai: 'ลงทะเบียน', + mandarin: '注册' }, createAccount: { english: 'Create Account', @@ -1017,7 +1558,11 @@ export const localizations = { brazilian_portuguese: 'Criar Conta', tok_pisin: 'Mekim Account', indonesian: 'Buat Akun', - nepali: 'खाता सिर्जना गर्नुहोस्' + nepali: 'खाता सिर्जना गर्नुहोस्', + hindi: 'खाता बनाएं', + burmese: 'အကောင့် ဖန်တီးပါ', + thai: 'สร้างบัญชี', + mandarin: '创建账户' }, registrationFail: { english: 'Registration failed', @@ -1025,7 +1570,11 @@ export const localizations = { brazilian_portuguese: 'Falha no registro', tok_pisin: 'Registration i no inap', indonesian: 'Pendaftaran gagal', - nepali: 'दर्ता असफल' + nepali: 'दर्ता असफल', + hindi: 'पंजीकरण विफल', + burmese: 'မှတ်ပုံတင်ခြင်း မအောင်မြင်ပါ', + thai: 'การลงทะเบียนล้มเหลว', + mandarin: '注册失败' }, registrationSuccess: { english: 'Registration successful', @@ -1033,7 +1582,11 @@ export const localizations = { brazilian_portuguese: 'Registro bem-sucedido', tok_pisin: 'Registration i orait', indonesian: 'Pendaftaran berhasil', - nepali: 'दर्ता सफल' + nepali: 'दर्ता सफल', + hindi: 'पंजीकरण सफल', + burmese: 'မှတ်ပုံတင်ခြင်း အောင်မြင်ပါသည်', + thai: 'การลงทะเบียนสำเร็จ', + mandarin: '注册成功' }, resetPassword: { english: 'Reset Password', @@ -1041,7 +1594,11 @@ export const localizations = { brazilian_portuguese: 'Redefinir Senha', tok_pisin: 'Reset Password', indonesian: 'Reset Kata Sandi', - nepali: 'पासवर्ड रिसेट गर्नुहोस्' + nepali: 'पासवर्ड रिसेट गर्नुहोस्', + hindi: 'पासवर्ड रीसेट करें', + burmese: 'စကားဝှက်ကို ပြန်လည်သတ်မှတ်ပါ', + thai: 'รีเซ็ตรหัสผ่าน', + mandarin: '重置密码' }, returningHero: { english: 'Returning hero? Sign In', @@ -1049,7 +1606,11 @@ export const localizations = { brazilian_portuguese: 'Herói retornando? Faça Login', tok_pisin: 'Hero i kam bek? Sign In', indonesian: 'Pahlawan kembali? Masuk', - nepali: 'फर्किने नायक? साइन इन गर्नुहोस्' + nepali: 'फर्किने नायक? साइन इन गर्नुहोस्', + hindi: 'वापस आ रहे हैं? साइन इन करें', + burmese: 'ပြန်လာနေသော သူရဲကောင်းလား? အကောင့်ဝင်ပါ', + thai: 'กลับมาแล้ว? เข้าสู่ระบบ', + mandarin: '返回的英雄?登录' }, search: { english: 'Search...', @@ -1057,7 +1618,11 @@ export const localizations = { brazilian_portuguese: 'Buscar...', tok_pisin: 'Painim...', indonesian: 'Cari...', - nepali: 'खोज्नुहोस्...' + nepali: 'खोज्नुहोस्...', + hindi: 'खोजें...', + burmese: 'ရှာဖွေပါ...', + thai: 'ค้นหา...', + mandarin: '搜索...' }, searchAssets: { english: 'Search assets...', @@ -1065,7 +1630,11 @@ export const localizations = { brazilian_portuguese: 'Buscar recursos...', tok_pisin: 'Painim ol asset...', indonesian: 'Cari aset...', - nepali: 'एसेटहरू खोज्नुहोस्...' + nepali: 'एसेटहरू खोज्नुहोस्...', + hindi: 'एसेट खोजें...', + burmese: 'ပိုင်ဆိုင်မှုများကို ရှာဖွေပါ...', + thai: 'ค้นหาสินทรัพย์...', + mandarin: '搜索资产...' }, noAssetsFound: { english: 'No assets found', @@ -1073,7 +1642,11 @@ export const localizations = { brazilian_portuguese: 'Nenhum recurso encontrado', tok_pisin: 'No gat asset', indonesian: 'Tidak ada aset ditemukan', - nepali: 'कुनै एसेट फेला परेन' + nepali: 'कुनै एसेट फेला परेन', + hindi: 'कोई एसेट नहीं मिला', + burmese: 'ပိုင်ဆိုင်မှု မတွေ့ရှိပါ', + thai: 'ไม่พบสินทรัพย์', + mandarin: '未找到资产' }, nothingHereYet: { english: 'Nothing here yet!', @@ -1081,7 +1654,11 @@ export const localizations = { brazilian_portuguese: '¡Nada aqui ainda!', tok_pisin: 'I no gat here yet!', indonesian: 'Belum ada di sini!', - nepali: 'यहाँ अझै केही छैन!' + nepali: 'यहाँ अझै केही छैन!', + hindi: 'यहाँ अभी तक कुछ नहीं!', + burmese: 'ဤနေရာတွင် မည်သည့်အရာမျှ မရှိသေးပါ!', + thai: 'ยังไม่มีอะไรที่นี่!', + mandarin: '这里还没有任何内容!' }, searchQuests: { english: 'Search quests...', @@ -1089,7 +1666,11 @@ export const localizations = { brazilian_portuguese: 'Buscar missões...', tok_pisin: 'Painim ol quest...', indonesian: 'Cari misi...', - nepali: 'क्वेस्टहरू खोज्नुहोस्...' + nepali: 'क्वेस्टहरू खोज्नुहोस्...', + hindi: 'क्वेस्ट खोजें...', + burmese: 'Quest များကို ရှာဖွေပါ...', + thai: 'ค้นหาเควสต์...', + mandarin: '搜索任务...' }, selectItem: { english: 'Select item', @@ -1097,7 +1678,11 @@ export const localizations = { brazilian_portuguese: 'Selecionar item', tok_pisin: 'Makim item', indonesian: 'Pilih item', - nepali: 'वस्तु चयन गर्नुहोस्' + nepali: 'वस्तु चयन गर्नुहोस्', + hindi: 'आइटम चुनें', + burmese: 'အရာဝတ္ထုကို ရွေးချယ်ပါ', + thai: 'เลือกรายการ', + mandarin: '选择项目' }, selectLanguage: { english: 'Please select a language', @@ -1105,7 +1690,11 @@ export const localizations = { brazilian_portuguese: 'Por favor, selecione um idioma', tok_pisin: 'Plis makim wanpela tokples', indonesian: 'Silakan pilih bahasa', - nepali: 'कृपया एउटा भाषा चयन गर्नुहोस्' + nepali: 'कृपया एउटा भाषा चयन गर्नुहोस्', + hindi: 'कृपया एक भाषा चुनें', + burmese: 'ကျေးဇူးပြု၍ ဘာသာစကားတစ်ခုကို ရွေးချယ်ပါ', + thai: 'กรุณาเลือกภาษา', + mandarin: '请选择一种语言' }, selectRegion: { english: 'Select Region', @@ -1113,7 +1702,11 @@ export const localizations = { brazilian_portuguese: 'Selecionar Região', tok_pisin: 'Makim Region', indonesian: 'Pilih Wilayah', - nepali: 'क्षेत्र चयन गर्नुहोस्' + nepali: 'क्षेत्र चयन गर्नुहोस्', + hindi: 'क्षेत्र चुनें', + burmese: 'ဒေသကို ရွေးချယ်ပါ', + thai: 'เลือกภูมิภาค', + mandarin: '选择地区' }, selectRegionToFilterLanguages: { english: 'Select a region to see languages from that area', @@ -1121,7 +1714,11 @@ export const localizations = { brazilian_portuguese: 'Selecionar uma região para ver idiomas dessa área', tok_pisin: 'Makim wanpela region long lukim ol tokples bilong ples ya', indonesian: 'Pilih wilayah untuk melihat bahasa dari area tersebut', - nepali: 'त्यस क्षेत्रका भाषाहरू हेर्न एउटा क्षेत्र चयन गर्नुहोस्' + nepali: 'त्यस क्षेत्रका भाषाहरू हेर्न एउटा क्षेत्र चयन गर्नुहोस्', + hindi: 'उस क्षेत्र की भाषाएं देखने के लिए एक क्षेत्र चुनें', + burmese: 'ထိုဒေသမှ ဘာသာစကားများကို ကြည့်ရှုရန် ဒေသတစ်ခုကို ရွေးချယ်ပါ', + thai: 'เลือกภูมิภาคเพื่อดูภาษาจากพื้นที่นั้น', + mandarin: '选择一个地区以查看该地区的语言' }, selectYourLanguage: { english: 'Select Your Language', @@ -1129,7 +1726,11 @@ export const localizations = { brazilian_portuguese: 'Selecionar Seu Idioma', tok_pisin: 'Makim Tokples Bilong Yu', indonesian: 'Pilih Bahasa Anda', - nepali: 'आफ्नो भाषा चयन गर्नुहोस्' + nepali: 'आफ्नो भाषा चयन गर्नुहोस्', + hindi: 'अपनी भाषा चुनें', + burmese: 'သင်၏ ဘာသာစကားကို ရွေးချယ်ပါ', + thai: 'เลือกภาษาของคุณ', + mandarin: '选择您的语言' }, createLanguage: { english: 'Create Language', @@ -1137,7 +1738,11 @@ export const localizations = { brazilian_portuguese: 'Criar Idioma', tok_pisin: 'Mekim Tokples', indonesian: 'Buat Bahasa', - nepali: 'भाषा सिर्जना गर्नुहोस्' + nepali: 'भाषा सिर्जना गर्नुहोस्', + hindi: 'भाषा बनाएं', + burmese: 'ဘာသာစကား ဖန်တီးပါ', + thai: 'สร้างภาษา', + mandarin: '创建语言' }, createNewLanguage: { english: 'Create New Language', @@ -1145,7 +1750,11 @@ export const localizations = { brazilian_portuguese: 'Criar Novo Idioma', tok_pisin: 'Mekim Nupela Tokples', indonesian: 'Buat Bahasa Baru', - nepali: 'नयाँ भाषा सिर्जना गर्नुहोस्' + nepali: 'नयाँ भाषा सिर्जना गर्नुहोस्', + hindi: 'नई भाषा बनाएं', + burmese: 'ဘာသာစကား အသစ် ဖန်တီးပါ', + thai: 'สร้างภาษาใหม่', + mandarin: '创建新语言' }, languageNotInList: { english: 'My language is not in the list', @@ -1153,7 +1762,11 @@ export const localizations = { brazilian_portuguese: 'Meu idioma não está na lista', tok_pisin: 'Tokples bilong mi i no stap long list', indonesian: 'Bahasa saya tidak ada dalam daftar', - nepali: 'मेरो भाषा सूचीमा छैन' + nepali: 'मेरो भाषा सूचीमा छैन', + hindi: 'मेरी भाषा सूची में नहीं है', + burmese: 'ကျွန်ုပ်၏ ဘာသာစကားသည် စာရင်းတွင် မပါဝင်ပါ', + thai: 'ภาษาของฉันไม่อยู่ในรายการ', + mandarin: '我的语言不在列表中' }, willCreateLanguage: { english: 'Will create language', @@ -1161,7 +1774,11 @@ export const localizations = { brazilian_portuguese: 'Criará idioma', tok_pisin: 'Bai mekim tokples', indonesian: 'Akan membuat bahasa', - nepali: 'भाषा सिर्जना गरिनेछ' + nepali: 'भाषा सिर्जना गरिनेछ', + hindi: 'भाषा बनाई जाएगी', + burmese: 'ဘာသာစကားကို ဖန်တီးပါမည်', + thai: 'จะสร้างภาษา', + mandarin: '将创建语言' }, nativeName: { english: 'Native Name', @@ -1169,7 +1786,11 @@ export const localizations = { brazilian_portuguese: 'Nome Nativo', tok_pisin: 'Nem Bilong Tokples', indonesian: 'Nama Asli', - nepali: 'स्थानीय नाम' + nepali: 'स्थानीय नाम', + hindi: 'मूल नाम', + burmese: 'မူရင်းအမည်', + thai: 'ชื่อพื้นเมือง', + mandarin: '本族语名称' }, englishName: { english: 'English Name', @@ -1177,7 +1798,11 @@ export const localizations = { brazilian_portuguese: 'Nome em Inglês', tok_pisin: 'Nem Long English', indonesian: 'Nama dalam Bahasa Inggris', - nepali: 'अंग्रेजी नाम' + nepali: 'अंग्रेजी नाम', + hindi: 'अंग्रेजी नाम', + burmese: 'အင်္ဂလိပ်အမည်', + thai: 'ชื่อภาษาอังกฤษ', + mandarin: '英文名称' }, iso6393Code: { english: 'ISO 639-3 Code', @@ -1185,7 +1810,11 @@ export const localizations = { brazilian_portuguese: 'Código ISO 639-3', tok_pisin: 'ISO 639-3 Code', indonesian: 'Kode ISO 639-3', - nepali: 'ISO 639-3 कोड' + nepali: 'ISO 639-3 कोड', + hindi: 'ISO 639-3 कोड', + burmese: 'ISO 639-3 ကုဒ်', + thai: 'รหัส ISO 639-3', + mandarin: 'ISO 639-3 代码' }, locale: { english: 'Locale', @@ -1193,7 +1822,11 @@ export const localizations = { brazilian_portuguese: 'Idioma', tok_pisin: 'Locale', indonesian: 'Lokalisasi', - nepali: 'लोकेल' + nepali: 'लोकेल', + hindi: 'लोकेल', + burmese: 'ဒေသ', + thai: 'โลแคล', + mandarin: '区域设置' }, createAndContinue: { english: 'Create and Continue', @@ -1201,7 +1834,11 @@ export const localizations = { brazilian_portuguese: 'Criar e Continuar', tok_pisin: 'Mekim na Go Long', indonesian: 'Buat dan Lanjutkan', - nepali: 'सिर्जना गर्नुहोस् र जारी राख्नुहोस्' + nepali: 'सिर्जना गर्नुहोस् र जारी राख्नुहोस्', + hindi: 'बनाएं और जारी रखें', + burmese: 'ဖန်တီးပြီး ဆက်လုပ်ပါ', + thai: 'สร้างและดำเนินการต่อ', + mandarin: '创建并继续' }, whatWouldYouLikeToCreate: { english: 'What would you like to create?', @@ -1209,7 +1846,11 @@ export const localizations = { brazilian_portuguese: 'O que você gostaria de criar?', tok_pisin: 'Wanem samting yu laik mekim?', indonesian: 'Apa yang ingin Anda buat?', - nepali: 'तपाईं के सिर्जना गर्न चाहनुहुन्छ?' + nepali: 'तपाईं के सिर्जना गर्न चाहनुहुन्छ?', + hindi: 'आप क्या बनाना चाहेंगे?', + burmese: 'သင်ဘာကို ဖန်တီးလိုပါသလဲ?', + thai: 'คุณต้องการสร้างอะไร?', + mandarin: '您想创建什么?' }, createBibleProject: { english: 'Bible', @@ -1217,7 +1858,11 @@ export const localizations = { brazilian_portuguese: 'Bíblia', tok_pisin: 'Baibel', indonesian: 'Alkitab', - nepali: 'बाइबल' + nepali: 'बाइबल', + hindi: 'बाइबल', + burmese: 'သမ္မာကျမ်းစာ', + thai: 'พระคัมภีร์', + mandarin: '圣经' }, translateBibleIntoYourLanguage: { english: 'Translate the Bible into your language', @@ -1225,7 +1870,11 @@ export const localizations = { brazilian_portuguese: 'Traduza a Bíblia para o seu idioma', tok_pisin: 'Translate Baibel long tokples bilong yu', indonesian: 'Terjemahkan Alkitab ke bahasa Anda', - nepali: 'बाइबललाई आफ्नो भाषामा अनुवाद गर्नुहोस्' + nepali: 'बाइबललाई आफ्नो भाषामा अनुवाद गर्नुहोस्', + hindi: 'बाइबल को अपनी भाषा में अनुवाद करें', + burmese: 'သမ္မာကျမ်းစာကို သင်၏ ဘာသာစကားသို့ ဘာသာပြန်ဆိုပါ', + thai: 'แปลพระคัมภีร์เป็นภาษาของคุณ', + mandarin: '将圣经翻译成您的语言' }, createOtherProject: { english: 'Other Translation', @@ -1233,7 +1882,11 @@ export const localizations = { brazilian_portuguese: 'Outra Tradução', tok_pisin: 'Narapela Translation', indonesian: 'Terjemahan Lain', - nepali: 'अन्य अनुवाद' + nepali: 'अन्य अनुवाद', + hindi: 'अन्य अनुवाद', + burmese: 'အခြား ဘာသာပြန်ဆိုချက်', + thai: 'การแปลอื่นๆ', + mandarin: '其他翻译' }, createGeneralTranslationProject: { english: 'Create a general translation project', @@ -1241,7 +1894,11 @@ export const localizations = { brazilian_portuguese: 'Criar um projeto de tradução geral', tok_pisin: 'Mekim wanpela project long translate ol samting', indonesian: 'Buat proyek terjemahan umum', - nepali: 'सामान्य अनुवाद प्रोजेक्ट सिर्जना गर्नुहोस्' + nepali: 'सामान्य अनुवाद प्रोजेक्ट सिर्जना गर्नुहोस्', + hindi: 'एक सामान्य अनुवाद परियोजना बनाएं', + burmese: 'အထွေထွေ ဘာသာပြန်ဆိုချက် စီမံကိန်း ဖန်တီးပါ', + thai: 'สร้างโครงการแปลทั่วไป', + mandarin: '创建通用翻译项目' }, selectProject: { english: 'Select Project', @@ -1249,7 +1906,11 @@ export const localizations = { brazilian_portuguese: 'Selecionar Projeto', tok_pisin: 'Makim Project', indonesian: 'Pilih Proyek', - nepali: 'प्रोजेक्ट चयन गर्नुहोस्' + nepali: 'प्रोजेक्ट चयन गर्नुहोस्', + hindi: 'परियोजना चुनें', + burmese: 'စီမံကိန်းကို ရွေးချယ်ပါ', + thai: 'เลือกโครงการ', + mandarin: '选择项目' }, createFirstProject: { english: 'Create First Project', @@ -1257,7 +1918,11 @@ export const localizations = { brazilian_portuguese: 'Criar Primeiro Projeto', tok_pisin: 'Mekim Nambawan Project', indonesian: 'Buat Proyek Pertama', - nepali: 'पहिलो प्रोजेक्ट सिर्जना गर्नुहोस्' + nepali: 'पहिलो प्रोजेक्ट सिर्जना गर्नुहोस्', + hindi: 'पहली परियोजना बनाएं', + burmese: 'ပထမဆုံး စီမံကိန်း ဖန်တီးပါ', + thai: 'สร้างโครงการแรก', + mandarin: '创建第一个项目' }, createNewProject: { english: 'Create New Project', @@ -1265,7 +1930,11 @@ export const localizations = { nepali: 'नयाँ प्रोजेक्ट सिर्जना गर्नुहोस्', brazilian_portuguese: 'Criar Novo Projeto', tok_pisin: 'Mekim Nupela Project', - indonesian: 'Buat Proyek Baru' + indonesian: 'Buat Proyek Baru', + hindi: 'नई परियोजना बनाएं', + burmese: 'စီမံကိန်း အသစ် ဖန်တီးပါ', + thai: 'สร้างโครงการใหม่', + mandarin: '创建新项目' }, existingProjectsInLanguage: { english: 'Existing projects in {language}', @@ -1273,7 +1942,11 @@ export const localizations = { brazilian_portuguese: 'Projetos existentes em {language}', tok_pisin: 'Ol project i stap pinis long {language}', indonesian: 'Proyek yang ada dalam {language}', - nepali: '{language} मा अवस्थित प्रोजेक्टहरू' + nepali: '{language} मा अवस्थित प्रोजेक्टहरू', + hindi: '{language} में मौजूदा परियोजनाएं', + burmese: '{language} တွင် ရှိနေသော စီမံကိန်းများ', + thai: 'โครงการที่มีอยู่ใน {language}', + mandarin: '{language} 中的现有项目' }, noProjectsInLanguage: { english: 'No projects yet in {language}', @@ -1281,7 +1954,11 @@ export const localizations = { brazilian_portuguese: 'Ainda não há projetos em {language}', tok_pisin: 'I no gat project yet long {language}', indonesian: 'Belum ada proyek dalam {language}', - nepali: '{language} मा अहिलेसम्म कुनै प्रोजेक्ट छैन' + nepali: '{language} मा अहिलेसम्म कुनै प्रोजेक्ट छैन', + hindi: '{language} में अभी तक कोई परियोजना नहीं', + burmese: '{language} တွင် အခုထိ စီမံကိန်း မရှိသေးပါ', + thai: 'ยังไม่มีโครงการใน {language}', + mandarin: '{language} 中还没有项目' }, searchLanguages: { english: 'Search languages...', @@ -1289,7 +1966,11 @@ export const localizations = { brazilian_portuguese: 'Pesquisar idiomas...', tok_pisin: 'Painim ol tokples...', indonesian: 'Cari bahasa...', - nepali: 'भाषाहरू खोज्नुहोस्...' + nepali: 'भाषाहरू खोज्नुहोस्...', + hindi: 'भाषाएं खोजें...', + burmese: 'ဘာသာစကားများကို ရှာဖွေပါ...', + thai: 'ค้นหาภาษา...', + mandarin: '搜索语言...' }, noLanguagesFound: { english: 'No languages found', @@ -1297,7 +1978,11 @@ export const localizations = { brazilian_portuguese: 'Nenhum idioma encontrado', tok_pisin: 'I no gat tokples', indonesian: 'Tidak ada bahasa ditemukan', - nepali: 'कुनै भाषा फेला परेन' + nepali: 'कुनै भाषा फेला परेन', + hindi: 'कोई भाषा नहीं मिली', + burmese: 'ဘာသာစကား မတွေ့ရှိပါ', + thai: 'ไม่พบภาษา', + mandarin: '未找到语言' }, noLanguagesInRegion: { english: @@ -1311,7 +1996,13 @@ export const localizations = { indonesian: 'Tidak ada bahasa ditemukan di wilayah ini. Anda dapat membuat bahasa baru di bawah ini.', nepali: - 'यस क्षेत्रमा कुनै भाषा फेला परेन। तपाईं तल नयाँ भाषा सिर्जना गर्न सक्नुहुन्छ।' + 'यस क्षेत्रमा कुनै भाषा फेला परेन। तपाईं तल नयाँ भाषा सिर्जना गर्न सक्नुहुन्छ।', + hindi: + 'इस क्षेत्र में कोई भाषा नहीं मिली। आप नीचे एक नई भाषा बना सकते हैं।', + burmese: + 'ဤဒေသတွင် ဘာသာစကား မတွေ့ရှိပါ။ သင်သည် အောက်တွင် ဘာသာစကား အသစ် ဖန်တီးနိုင်သည်။', + thai: 'ไม่พบภาษาในภูมิภาคนี้ คุณสามารถสร้างภาษาใหม่ด้านล่างได้', + mandarin: '此地区未找到语言。您可以在下面创建新语言。' }, typeToSearch: { english: 'Type at least {min} characters to search', @@ -1319,7 +2010,11 @@ export const localizations = { brazilian_portuguese: 'Digite pelo menos {min} caracteres para pesquisar', tok_pisin: 'Raitim {min} leta bipo painim', indonesian: 'Ketik setidaknya {min} karakter untuk mencari', - nepali: 'खोज्न कम्तिमा {min} अक्षर टाइप गर्नुहोस्' + nepali: 'खोज्न कम्तिमा {min} अक्षर टाइप गर्नुहोस्', + hindi: 'खोजने के लिए कम से कम {min} अक्षर टाइप करें', + burmese: 'ရှာဖွေရန် အနည်းဆုံး {min} စာလုံး ရိုက်ထည့်ပါ', + thai: 'พิมพ์อย่างน้อย {min} ตัวอักษรเพื่อค้นหา', + mandarin: '输入至少 {min} 个字符进行搜索' }, selectTemplate: { english: 'Please select a template', @@ -1327,7 +2022,11 @@ export const localizations = { brazilian_portuguese: 'Por favor, selecione uma planta', tok_pisin: 'Plis makim wanpela template', indonesian: 'Silakan pilih template', - nepali: 'कृपया एउटा टेम्प्लेट छान्नुहोस्' + nepali: 'कृपया एउटा टेम्प्लेट छान्नुहोस्', + hindi: 'कृपया एक टेम्प्लेट चुनें', + burmese: 'ကျေးဇူးပြု၍ ပုံစံတစ်ခုကို ရွေးချယ်ပါ', + thai: 'กรุณาเลือกเทมเพลต', + mandarin: '请选择一个模板' }, sendResetEmail: { english: 'Send Reset Email', @@ -1335,7 +2034,11 @@ export const localizations = { brazilian_portuguese: 'Enviar E-mail de Redefinição', tok_pisin: 'Salim Reset Email', indonesian: 'Kirim Email Reset', - nepali: 'रिसेट इमेल पठाउनुहोस्' + nepali: 'रिसेट इमेल पठाउनुहोस्', + hindi: 'रीसेट ईमेल भेजें', + burmese: 'ပြန်လည်သတ်မှတ်ရန် အီးမေးလ်ပို့ပါ', + thai: 'ส่งอีเมลรีเซ็ต', + mandarin: '发送重置电子邮件' }, signIn: { english: 'Sign In', @@ -1343,7 +2046,11 @@ export const localizations = { brazilian_portuguese: 'Entrar', tok_pisin: 'Sign In', indonesian: 'Masuk', - nepali: 'साइन इन गर्नुहोस्' + nepali: 'साइन इन गर्नुहोस्', + hindi: 'साइन इन करें', + burmese: 'အကောင့်ဝင်ပါ', + thai: 'เข้าสู่ระบบ', + mandarin: '登录' }, signInToSaveOrContribute: { english: 'Sign in to save or contribute to projects', @@ -1351,7 +2058,11 @@ export const localizations = { brazilian_portuguese: 'Entre para salvar ou contribuir com projetos', tok_pisin: 'Sign in long seivim o helpim ol project', indonesian: 'Masuk untuk menyimpan atau berkontribusi pada proyek', - nepali: 'प्रोजेक्टहरू सेभ गर्न वा योगदान गर्न साइन इन गर्नुहोस्' + nepali: 'प्रोजेक्टहरू सेभ गर्न वा योगदान गर्न साइन इन गर्नुहोस्', + hindi: 'परियोजनाओं को सहेजने या योगदान देने के लिए साइन इन करें', + burmese: 'စီမံကိန်းများကို သိမ်းဆည်းရန် သို့မဟုတ် ပံ့ပိုးရန် အကောင့်ဝင်ပါ', + thai: 'เข้าสู่ระบบเพื่อบันทึกหรือมีส่วนร่วมในโครงการ', + mandarin: '登录以保存或为项目做出贡献' }, orBrowseAllProjects: { english: 'Or browse all public projects', @@ -1359,7 +2070,11 @@ export const localizations = { brazilian_portuguese: 'Ou navegue por todos os projetos públicos', tok_pisin: 'O lukluk long olgeta public project', indonesian: 'Atau jelajahi semua proyek publik', - nepali: 'वा सबै सार्वजनिक प्रोजेक्टहरू ब्राउज गर्नुहोस्' + nepali: 'वा सबै सार्वजनिक प्रोजेक्टहरू ब्राउज गर्नुहोस्', + hindi: 'या सभी सार्वजनिक परियोजनाएं ब्राउज़ करें', + burmese: 'သို့မဟုတ် လူထုစီမံကိန်းအားလုံးကို ရှာဖွေကြည့်ရှုပါ', + thai: 'หรือเรียกดูโครงการสาธารณะทั้งหมด', + mandarin: '或浏览所有公共项目' }, viewAllProjects: { english: 'View All Projects', @@ -1367,7 +2082,11 @@ export const localizations = { brazilian_portuguese: 'Ver Todos os Projetos', tok_pisin: 'Lukim Olgeta Project', indonesian: 'Lihat Semua Proyek', - nepali: 'सबै प्रोजेक्टहरू हेर्नुहोस्' + nepali: 'सबै प्रोजेक्टहरू हेर्नुहोस्', + hindi: 'सभी परियोजनाएं देखें', + burmese: 'စီမံကိန်းအားလုံးကို ကြည့်ရှုပါ', + thai: 'ดูโครงการทั้งหมด', + mandarin: '查看所有项目' }, signInError: { english: 'Something went wrong… Please, check your email and password.', @@ -1377,7 +2096,12 @@ export const localizations = { tok_pisin: 'Samting i rong... Plis checkum email na password bilong yu.', indonesian: 'Terjadi kesalahan... Silakan periksa email dan kata sandi Anda.', - nepali: 'केही गलत भयो… कृपया आफ्नो इमेल र पासवर्ड जाँच गर्नुहोस्।' + nepali: 'केही गलत भयो… कृपया आफ्नो इमेल र पासवर्ड जाँच गर्नुहोस्।', + hindi: 'कुछ गलत हो गया… कृपया अपना ईमेल और पासवर्ड जांचें।', + burmese: + 'တစ်ခုခု မှားယွင်းနေပါသည်… ကျေးဇူးပြု၍ သင်၏ အီးမေးလ်နှင့် စကားဝှက်ကို စစ်ဆေးပါ။', + thai: 'เกิดข้อผิดพลาด... กรุณาตรวจสอบอีเมลและรหัสผ่านของคุณ', + mandarin: '出了点问题…请检查您的电子邮件和密码。' }, logOut: { english: 'Log Out', @@ -1385,7 +2109,11 @@ export const localizations = { brazilian_portuguese: 'Sair', tok_pisin: 'Log Out', indonesian: 'Keluar', - nepali: 'लग आउट गर्नुहोस्' + nepali: 'लग आउट गर्नुहोस्', + hindi: 'लॉग आउट करें', + burmese: 'အကောင့်ထွက်ပါ', + thai: 'ออกจากระบบ', + mandarin: '登出' }, sortBy: { english: 'Sort by', @@ -1393,7 +2121,11 @@ export const localizations = { brazilian_portuguese: 'Ordenar por', tok_pisin: 'Sortim long', indonesian: 'Urutkan berdasarkan', - nepali: 'क्रमबद्ध गर्नुहोस्' + nepali: 'क्रमबद्ध गर्नुहोस्', + hindi: 'क्रमबद्ध करें', + burmese: 'အလိုက်စဉ်ပါ', + thai: 'เรียงตาม', + mandarin: '排序方式' }, source: { english: 'Source', @@ -1401,7 +2133,11 @@ export const localizations = { brazilian_portuguese: 'Fonte', tok_pisin: 'Source', indonesian: 'Sumber', - nepali: 'स्रोत' + nepali: 'स्रोत', + hindi: 'स्रोत', + burmese: 'ရင်းမြစ်', + thai: 'แหล่งที่มา', + mandarin: '来源' }, submit: { english: 'Submit', @@ -1409,7 +2145,11 @@ export const localizations = { brazilian_portuguese: 'Enviar', tok_pisin: 'Salim', indonesian: 'Kirim', - nepali: 'पेश गर्नुहोस्' + nepali: 'पेश गर्नुहोस्', + hindi: 'जमा करें', + burmese: 'တင်သွင်းပါ', + thai: 'ส่ง', + mandarin: '提交' }, success: { english: 'Success', @@ -1417,7 +2157,11 @@ export const localizations = { brazilian_portuguese: 'Sucesso', tok_pisin: 'Orait', indonesian: 'Berhasil', - nepali: 'सफल' + nepali: 'सफल', + hindi: 'सफलता', + burmese: 'အောင်မြင်မှု', + thai: 'สำเร็จ', + mandarin: '成功' }, target: { english: 'Target', @@ -1425,7 +2169,11 @@ export const localizations = { brazilian_portuguese: 'Alvo', tok_pisin: 'Target', indonesian: 'Target', - nepali: 'लक्ष्य' + nepali: 'लक्ष्य', + hindi: 'लक्ष्य', + burmese: 'ပစ်မှတ်', + thai: 'เป้าหมาย', + mandarin: '目标' }, username: { english: 'Username', @@ -1433,7 +2181,11 @@ export const localizations = { brazilian_portuguese: 'Nome de usuário', tok_pisin: 'Username', indonesian: 'Nama pengguna', - nepali: 'प्रयोगकर्ता नाम' + nepali: 'प्रयोगकर्ता नाम', + hindi: 'उपयोगकर्ता नाम', + burmese: 'အသုံးပြုသူအမည်', + thai: 'ชื่อผู้ใช้', + mandarin: '用户名' }, usernameRequired: { english: 'Username is required', @@ -1441,7 +2193,11 @@ export const localizations = { brazilian_portuguese: 'Nome de usuário é obrigatório', tok_pisin: 'Username i mas', indonesian: 'Nama pengguna diperlukan', - nepali: 'प्रयोगकर्ता नाम आवश्यक छ' + nepali: 'प्रयोगकर्ता नाम आवश्यक छ', + hindi: 'उपयोगकर्ता नाम आवश्यक है', + burmese: 'အသုံးပြုသူအမည် လိုအပ်ပါသည်', + thai: 'ต้องใช้ชื่อผู้ใช้', + mandarin: '需要用户名' }, votes: { english: 'Votes', @@ -1449,13 +2205,23 @@ export const localizations = { brazilian_portuguese: 'Votos', tok_pisin: 'Ol Vote', indonesian: 'Suara', - nepali: 'मतहरू' + nepali: 'मतहरू', + hindi: 'वोट', + burmese: 'မဲများ', + thai: 'คะแนน', + mandarin: '投票' }, voting: { english: 'Voting', spanish: 'Votación', brazilian_portuguese: 'Votação', - nepali: 'मतदान' + tok_pisin: 'Voting', + indonesian: 'Pemungutan Suara', + nepali: 'मतदान', + hindi: 'मतदान', + burmese: 'မဲပေးခြင်း', + thai: 'การลงคะแนน', + mandarin: '投票' }, warning: { english: 'Warning', @@ -1463,7 +2229,11 @@ export const localizations = { brazilian_portuguese: 'Aviso', tok_pisin: 'Warning', indonesian: 'Peringatan', - nepali: 'चेतावनी' + nepali: 'चेतावनी', + hindi: 'चेतावनी', + burmese: 'သတိပေးချက်', + thai: 'คำเตือน', + mandarin: '警告' }, welcome: { english: 'Welcome back, hero!', @@ -1471,7 +2241,11 @@ export const localizations = { brazilian_portuguese: 'Bem-vindo de volta, herói!', tok_pisin: 'Welkam bek, hero!', indonesian: 'Selamat datang kembali, pahlawan!', - nepali: 'फेरि स्वागत छ, नायक!' + nepali: 'फेरि स्वागत छ, नायक!', + hindi: 'वापसी पर स्वागत है, नायक!', + burmese: 'ပြန်လာရန် ကြိုဆိုပါသည်၊ သူရဲကောင်း!', + thai: 'ยินดีต้อนรับกลับมา ฮีโร่!', + mandarin: '欢迎回来,英雄!' }, welcomeToApp: { english: 'Welcome', @@ -1479,7 +2253,11 @@ export const localizations = { brazilian_portuguese: 'Bem-vindo', tok_pisin: 'Welkam', indonesian: 'Selamat datang', - nepali: 'स्वागत छ' + nepali: 'स्वागत छ', + hindi: 'स्वागत है', + burmese: 'ကြိုဆိုပါသည်', + thai: 'ยินดีต้อนรับ', + mandarin: '欢迎' }, recentlyVisited: { english: 'Recently Visited', @@ -1487,7 +2265,11 @@ export const localizations = { brazilian_portuguese: 'Visitados Recentemente', tok_pisin: 'Nupela taim visitim', indonesian: 'Baru Dikunjungi', - nepali: 'हालसालै भ्रमण गरिएको' + nepali: 'हालसालै भ्रमण गरिएको', + hindi: 'हाल ही में देखा गया', + burmese: 'မကြာသေးမီ လည်ပတ်ခဲ့သည်', + thai: 'เยี่ยมชมล่าสุด', + mandarin: '最近访问' }, assets: { english: 'Assets', @@ -1495,13 +2277,23 @@ export const localizations = { brazilian_portuguese: 'Recursos', tok_pisin: 'Ol Asset', indonesian: 'Aset', - nepali: 'एसेटहरू' + nepali: 'एसेटहरू', + hindi: 'एसेट', + burmese: 'ပိုင်ဆိုင်မှုများ', + thai: 'สินทรัพย์', + mandarin: '资产' }, asset: { english: 'Asset', spanish: 'Recurso', brazilian_portuguese: 'Recurso', - nepali: 'एसेट' + tok_pisin: 'Asset', + indonesian: 'Aset', + nepali: 'एसेट', + hindi: 'एसेट', + burmese: 'ပိုင်ဆိုင်မှု', + thai: 'สินทรัพย์', + mandarin: '资产' }, deleteAssets: { english: 'Delete Assets', @@ -1509,7 +2301,11 @@ export const localizations = { brazilian_portuguese: 'Excluir recursos', tok_pisin: 'Rausim ol asset', indonesian: 'Hapus aset', - nepali: 'एसेटहरू मेटाउनुहोस्' + nepali: 'एसेटहरू मेटाउनुहोस्', + hindi: 'एसेट हटाएं', + burmese: 'ပိုင်ဆိုင်မှုများကို ဖျက်ပါ', + thai: 'ลบสินทรัพย์', + mandarin: '删除资产' }, deleteAssetsConfirmation: { english: @@ -1523,7 +2319,13 @@ export const localizations = { indonesian: 'Apakah Anda yakin ingin menghapus {count} aset? Tindakan ini tidak dapat dibatalkan.', nepali: - 'के तपाईं निश्चित हुनुहुन्छ कि तपाईं {count} एसेट(हरू) मेटाउन चाहनुहुन्छ? यो कार्य पूर्ववत गर्न सकिँदैन।' + 'के तपाईं निश्चित हुनुहुन्छ कि तपाईं {count} एसेट(हरू) मेटाउन चाहनुहुन्छ? यो कार्य पूर्ववत गर्न सकिँदैन।', + hindi: + 'क्या आप वाकई {count} एसेट हटाना चाहते हैं? यह कार्रवाई पूर्ववत नहीं की जा सकती।', + burmese: + 'သင်သည် {count} ပိုင်ဆိုင်မှုကို ဖျက်ရန် သေချာပါသလား? ဤလုပ်ဆောင်ချက်ကို ပြန်လည်ပြုပြင်မရပါ။', + thai: 'คุณแน่ใจหรือไม่ว่าต้องการลบสินทรัพย์ {count} รายการ? การดำเนินการนี้ไม่สามารถยกเลิกได้', + mandarin: '您确定要删除 {count} 个资产吗?此操作无法撤消。' }, selected: { english: 'selected', @@ -1531,7 +2333,11 @@ export const localizations = { brazilian_portuguese: 'selecionado(s)', tok_pisin: 'selected', indonesian: 'terpilih', - nepali: 'चयन गरिएको' + nepali: 'चयन गरिएको', + hindi: 'चयनित', + burmese: 'ရွေးချယ်ထားသည်', + thai: 'ที่เลือก', + mandarin: '已选择' }, delete: { english: 'Delete', @@ -1539,7 +2345,11 @@ export const localizations = { brazilian_portuguese: 'Excluir', tok_pisin: 'Rausim', indonesian: 'Hapus', - nepali: 'मेटाउनुहोस्' + nepali: 'मेटाउनुहोस्', + hindi: 'हटाएं', + burmese: 'ဖျက်ပါ', + thai: 'ลบ', + mandarin: '删除' }, mergeAssets: { english: 'Merge Assets', @@ -1547,7 +2357,11 @@ export const localizations = { brazilian_portuguese: 'Mesclar recursos', tok_pisin: 'Joinim ol asset', indonesian: 'Gabungkan aset', - nepali: 'एसेटहरू मर्ज गर्नुहोस्' + nepali: 'एसेटहरू मर्ज गर्नुहोस्', + hindi: 'एसेट मर्ज करें', + burmese: 'ပိုင်ဆိုင်မှုများကို ပေါင်းစပ်ပါ', + thai: 'รวมสินทรัพย์', + mandarin: '合并资产' }, mergeAssetsConfirmation: { english: @@ -1561,7 +2375,14 @@ export const localizations = { indonesian: 'Apakah Anda yakin ingin menggabungkan {count} aset? Segmen audio akan digabungkan ke aset pertama yang dipilih, dan yang lainnya akan dihapus.', nepali: - 'के तपाईं निश्चित हुनुहुन्छ कि तपाईं {count} एसेटहरू मर्ज गर्न चाहनुहुन्छ? अडियो खण्डहरू पहिलो चयन गरिएको एसेटमा संयोजन हुनेछन्, र अरूहरू मेटिनेछन्।' + 'के तपाईं निश्चित हुनुहुन्छ कि तपाईं {count} एसेटहरू मर्ज गर्न चाहनुहुन्छ? अडियो खण्डहरू पहिलो चयन गरिएको एसेटमा संयोजन हुनेछन्, र अरूहरू मेटिनेछन्।', + hindi: + 'क्या आप वाकई {count} एसेट मर्ज करना चाहते हैं? ऑडियो सेगमेंट पहले चयनित एसेट में संयोजित हो जाएंगे, और अन्य हटा दिए जाएंगे।', + burmese: + 'သင်သည် {count} ပိုင်ဆိုင်မှုများကို ပေါင်းစပ်ရန် သေချာပါသလား? အသံအပိုင်းများကို ပထမဆုံး ရွေးချယ်ထားသော ပိုင်ဆိုင်မှုသို့ ပေါင်းစပ်ပါမည်၊ အခြားများကို ဖျက်ပါမည်။', + thai: 'คุณแน่ใจหรือไม่ว่าต้องการรวมสินทรัพย์ {count} รายการ? ส่วนเสียงจะถูกรวมเข้ากับสินทรัพย์แรกที่เลือก และรายการอื่นๆ จะถูกลบ', + mandarin: + '您确定要合并 {count} 个资产吗?音频片段将合并到第一个选定的资产中,其他资产将被删除。' }, merge: { english: 'Merge', @@ -1569,7 +2390,11 @@ export const localizations = { brazilian_portuguese: 'Mesclar', tok_pisin: 'Joinim', indonesian: 'Gabungkan', - nepali: 'मर्ज गर्नुहोस्' + nepali: 'मर्ज गर्नुहोस्', + hindi: 'मर्ज करें', + burmese: 'ပေါင်းစပ်ပါ', + thai: 'รวม', + mandarin: '合并' }, failedToMergeAssets: { english: 'Failed to merge assets. Please try again.', @@ -1578,7 +2403,12 @@ export const localizations = { 'Falha ao mesclar recursos. Por favor, tente novamente.', tok_pisin: 'I no inap joinim ol asset. Plis traim gen.', indonesian: 'Gagal menggabungkan aset. Silakan coba lagi.', - nepali: 'एसेटहरू मर्ज गर्न असफल। कृपया पुन: प्रयास गर्नुहोस्।' + nepali: 'एसेटहरू मर्ज गर्न असफल। कृपया पुन: प्रयास गर्नुहोस्।', + hindi: 'एसेट मर्ज करने में विफल। कृपया पुनः प्रयास करें।', + burmese: + 'ပိုင်ဆိုင်မှုများကို ပေါင်းစပ်၍မရပါ။ ကျေးဇူးပြု၍ ထပ်မံကြိုးစားပါ။', + thai: 'รวมสินทรัพย์ไม่สำเร็จ กรุณาลองอีกครั้ง', + mandarin: '合并资产失败。请重试。' }, failedToDeleteAssets: { english: 'Failed to delete assets. Please try again.', @@ -1587,7 +2417,11 @@ export const localizations = { 'Falha ao excluir recursos. Por favor, tente novamente.', tok_pisin: 'I no inap rausim ol asset. Plis traim gen.', indonesian: 'Gagal menghapus aset. Silakan coba lagi.', - nepali: 'एसेटहरू मेटाउन असफल। कृपया पुन: प्रयास गर्नुहोस्।' + nepali: 'एसेटहरू मेटाउन असफल। कृपया पुन: प्रयास गर्नुहोस्।', + hindi: 'एसेट हटाने में विफल। कृपया पुनः प्रयास करें।', + burmese: 'ပိုင်ဆိုင်မှုများကို ဖျက်၍မရပါ။ ကျေးဇူးပြု၍ ထပ်မံကြိုးစားပါ။', + thai: 'ลบสินทรัพย์ไม่สำเร็จ กรุณาลองอีกครั้ง', + mandarin: '删除资产失败。请重试。' }, errorLoadingAssets: { english: 'Error loading assets', @@ -1595,7 +2429,11 @@ export const localizations = { brazilian_portuguese: 'Erro ao carregar recursos', tok_pisin: 'Rong long loadim ol asset', indonesian: 'Kesalahan memuat aset', - nepali: 'एसेटहरू लोड गर्न त्रुटि' + nepali: 'एसेटहरू लोड गर्न त्रुटि', + hindi: 'एसेट लोड करने में त्रुटि', + burmese: 'ပိုင်ဆိုင်မှုများကို လုပ်ဆောင်၍မရပါ', + thai: 'เกิดข้อผิดพลาดในการโหลดสินทรัพย์', + mandarin: '加载资产时出错' }, noAssetsYetStartRecording: { english: 'No assets yet. Start recording to create your first asset.', @@ -1608,7 +2446,13 @@ export const localizations = { indonesian: 'Belum ada aset. Mulai merekam untuk membuat aset pertama Anda.', nepali: - 'अहिलेसम्म कुनै एसेट छैन। तपाईंको पहिलो एसेट सिर्जना गर्न रेकर्डिङ सुरु गर्नुहोस्।' + 'अहिलेसम्म कुनै एसेट छैन। तपाईंको पहिलो एसेट सिर्जना गर्न रेकर्डिङ सुरु गर्नुहोस्।', + hindi: + 'अभी तक कोई एसेट नहीं। अपना पहला एसेट बनाने के लिए रिकॉर्डिंग शुरू करें।', + burmese: + 'အခုထိ ပိုင်ဆိုင်မှု မရှိသေးပါ။ သင်၏ ပထမဆုံး ပိုင်ဆိုင်မှုကို ဖန်တီးရန် မှတ်တမ်းတင်ခြင်းကို စတင်ပါ။', + thai: 'ยังไม่มีสินทรัพย์ เริ่มบันทึกเพื่อสร้างสินทรัพย์แรกของคุณ', + mandarin: '还没有资产。开始录制以创建您的第一个资产。' }, remaining: { english: 'remaining', @@ -1616,7 +2460,11 @@ export const localizations = { brazilian_portuguese: 'restante', tok_pisin: 'stap yet', indonesian: 'tersisa', - nepali: 'बाँकी' + nepali: 'बाँकी', + hindi: 'शेष', + burmese: 'ကျန်ရှိ', + thai: 'เหลืออยู่', + mandarin: '剩余' }, noNotifications: { english: 'No notifications', @@ -1624,7 +2472,11 @@ export const localizations = { brazilian_portuguese: 'Nenhuma notificação', tok_pisin: 'No gat notification', indonesian: 'Tidak ada notifikasi', - nepali: 'कुनै सूचना छैन' + nepali: 'कुनै सूचना छैन', + hindi: 'कोई सूचना नहीं', + burmese: 'အကြောင်းကြားချက် မရှိပါ', + thai: 'ไม่มีการแจ้งเตือน', + mandarin: '无通知' }, noNotificationsSubtext: { english: "You'll see project invitations and join requests here", @@ -1635,7 +2487,12 @@ export const localizations = { indonesian: 'Anda akan melihat undangan proyek dan permintaan bergabung di sini', nepali: - 'तपाईंले यहाँ प्रोजेक्ट निमन्त्रणा र सामेल हुने अनुरोधहरू देख्नुहुनेछ' + 'तपाईंले यहाँ प्रोजेक्ट निमन्त्रणा र सामेल हुने अनुरोधहरू देख्नुहुनेछ', + hindi: 'आप यहाँ परियोजना निमंत्रण और शामिल होने के अनुरोध देखेंगे', + burmese: + 'သင်သည် ဤနေရာတွင် စီမံကိန်း ဖိတ်ခေါ်မှုများနှင့် ပါဝင်ရန် တောင်းဆိုမှုများကို မြင်ရပါမည်', + thai: 'คุณจะเห็นคำเชิญโครงการและคำขอเข้าร่วมที่นี่', + mandarin: '您将在此处看到项目邀请和加入请求' }, notifications: { english: 'Notifications', @@ -1643,7 +2500,11 @@ export const localizations = { brazilian_portuguese: 'Notificações', tok_pisin: 'Ol Notification', indonesian: 'Notifikasi', - nepali: 'सूचनाहरू' + nepali: 'सूचनाहरू', + hindi: 'सूचनाएं', + burmese: 'အကြောင်းကြားချက်များ', + thai: 'การแจ้งเตือน', + mandarin: '通知' }, profile: { english: 'Profile', @@ -1651,7 +2512,11 @@ export const localizations = { brazilian_portuguese: 'Perfil', tok_pisin: 'Profile', indonesian: 'Profil', - nepali: 'प्रोफाइल' + nepali: 'प्रोफाइल', + hindi: 'प्रोफ़ाइल', + burmese: 'ကိုယ်ရေးအကျဉ်း', + thai: 'โปรไฟล์', + mandarin: '个人资料' }, settings: { english: 'Settings', @@ -1659,7 +2524,11 @@ export const localizations = { brazilian_portuguese: 'Configurações', tok_pisin: 'Settings', indonesian: 'Pengaturan', - nepali: 'सेटिङहरू' + nepali: 'सेटिङहरू', + hindi: 'सेटिंग्स', + burmese: 'ဆက်တင်များ', + thai: 'การตั้งค่า', + mandarin: '设置' }, changePassword: { english: 'Change Password', @@ -1667,7 +2536,11 @@ export const localizations = { brazilian_portuguese: 'Alterar Senha', tok_pisin: 'Senisim Password', indonesian: 'Ubah Kata Sandi', - nepali: 'पासवर्ड परिवर्तन गर्नुहोस्' + nepali: 'पासवर्ड परिवर्तन गर्नुहोस्', + hindi: 'पासवर्ड बदलें', + burmese: 'စကားဝှက်ကို ပြောင်းလဲပါ', + thai: 'เปลี่ยนรหัสผ่าน', + mandarin: '更改密码' }, currentPassword: { english: 'Current Password', @@ -1675,7 +2548,11 @@ export const localizations = { brazilian_portuguese: 'Senha Atual', tok_pisin: 'Password bilong nau', indonesian: 'Kata Sandi Saat Ini', - nepali: 'हालको पासवर्ड' + nepali: 'हालको पासवर्ड', + hindi: 'वर्तमान पासवर्ड', + burmese: 'လက်ရှိ စကားဝှက်', + thai: 'รหัสผ่านปัจจุบัน', + mandarin: '当前密码' }, newPassword: { english: 'New Password', @@ -1683,7 +2560,11 @@ export const localizations = { brazilian_portuguese: 'Nova Senha', tok_pisin: 'Nupela Password', indonesian: 'Kata Sandi Baru', - nepali: 'नयाँ पासवर्ड' + nepali: 'नयाँ पासवर्ड', + hindi: 'नया पासवर्ड', + burmese: 'စကားဝှက် အသစ်', + thai: 'รหัสผ่านใหม่', + mandarin: '新密码' }, onlineOnlyFeatures: { english: 'Password changes are only available when online', @@ -1693,7 +2574,12 @@ export const localizations = { 'Alterações de senha só estão disponíveis quando você está online', tok_pisin: 'Password senisim i ken long taim yu gat internet tasol', indonesian: 'Perubahan kata sandi hanya tersedia saat online', - nepali: 'पासवर्ड परिवर्तन अनलाइन हुँदा मात्र उपलब्ध छ' + nepali: 'पासवर्ड परिवर्तन अनलाइन हुँदा मात्र उपलब्ध छ', + hindi: 'पासवर्ड परिवर्तन केवल ऑनलाइन होने पर उपलब्ध हैं', + burmese: + 'စကားဝှက် ပြောင်းလဲမှုများသည် အင်တာနက်ရှိသောအချိန်တွင်သာ ရရှိနိုင်သည်', + thai: 'การเปลี่ยนรหัสผ่านมีให้เฉพาะเมื่อออนไลน์', + mandarin: '密码更改仅在在线时可用' }, accountDeletionRequiresOnline: { english: 'You must be online to delete your account', @@ -1701,7 +2587,11 @@ export const localizations = { brazilian_portuguese: 'Você deve estar online para excluir sua conta', tok_pisin: 'Yu mas gat internet long rausim account bilong yu', indonesian: 'Anda harus online untuk menghapus akun Anda', - nepali: 'आफ्नो खाता मेटाउन तपाईं अनलाइन हुनुपर्छ' + nepali: 'आफ्नो खाता मेटाउन तपाईं अनलाइन हुनुपर्छ', + hindi: 'अपना खाता हटाने के लिए आपको ऑनलाइन होना होगा', + burmese: 'သင်၏ အကောင့်ကို ဖျက်ရန် သင်သည် အင်တာနက်ရှိရမည်', + thai: 'คุณต้องออนไลน์เพื่อลบบัญชีของคุณ', + mandarin: '您必须在线才能删除您的账户' }, termsAndPrivacyTitle: { english: 'Terms & Privacy', @@ -1709,7 +2599,11 @@ export const localizations = { brazilian_portuguese: 'Termos e Privacidade', tok_pisin: 'Terms na Privacy', indonesian: 'Syarat & Privasi', - nepali: 'सर्तहरू र गोपनीयता' + nepali: 'सर्तहरू र गोपनीयता', + hindi: 'नियम और गोपनीयता', + burmese: 'စည်းမျဉ်းများနှင့် ကိုယ်ရေးလုံခြုံမှု', + thai: 'ข้อกำหนดและความเป็นส่วนตัว', + mandarin: '条款和隐私' }, verificationRequired: { english: 'Verification Required', @@ -1717,15 +2611,35 @@ export const localizations = { brazilian_portuguese: 'Verificação Necessária', tok_pisin: 'Verification i mas', indonesian: 'Verifikasi Diperlukan', - nepali: 'प्रमाणीकरण आवश्यक छ' + nepali: 'प्रमाणीकरण आवश्यक छ', + hindi: 'सत्यापन आवश्यक', + burmese: 'အတည်ပြုခြင်း လိုအပ်ပါသည်', + thai: 'ต้องยืนยัน', + mandarin: '需要验证' }, agreeToTerms: { - english: 'I have read and agree to the Terms & Privacy', - spanish: 'He leído y acepto los Términos y Privacidad', - brazilian_portuguese: 'Eu li e concordo com os Termos e Privacidade', - tok_pisin: 'Mi ridim na agri long Terms na Privacy', - indonesian: 'Saya telah membaca dan menyetujui Syarat & Privasi', - nepali: 'मैले सर्तहरू र गोपनीयता पढेको छु र स्वीकार गर्छु' + english: 'I have read and agree to the {link}', + spanish: 'He leído y acepto los {link}', + brazilian_portuguese: 'Eu li e concordo com os {link}', + tok_pisin: 'Mi ridim na agri long {link}', + indonesian: 'Saya telah membaca dan menyetujui {link}', + nepali: 'मैले {link} पढेको छु र स्वीकार गर्छु', + hindi: 'मैंने {link} पढ़ ली है और सहमत हूं', + burmese: 'ကျွန်ုပ်သည် {link} ကို ဖတ်ရှုပြီး သဘောတူပါသည်', + thai: 'ฉันได้อ่านและยอมรับ{link}แล้ว', + mandarin: '我已阅读并同意{link}' + }, + termsAndPrivacyLink: { + english: 'Terms & Privacy', + spanish: 'Términos y Privacidad', + brazilian_portuguese: 'Termos e Privacidade', + tok_pisin: 'Terms na Privacy', + indonesian: 'Syarat & Privasi', + nepali: 'सर्तहरू र गोपनीयता', + hindi: 'नियम और गोपनीयता', + burmese: 'စည်းမျဉ်းများနှင့် ကိုယ်ရေးလုံခြုံမှု', + thai: 'ข้อกำหนดและความเป็นส่วนตัว', + mandarin: '条款和隐私' }, viewTerms: { english: 'View Terms and Privacy', @@ -1733,7 +2647,11 @@ export const localizations = { brazilian_portuguese: 'Ver Termos e Privacidade', tok_pisin: 'Lukim Terms na Privacy', indonesian: 'Lihat Syarat dan Privasi', - nepali: 'सर्तहरू र गोपनीयता हेर्नुहोस्' + nepali: 'सर्तहरू र गोपनीयता हेर्नुहोस्', + hindi: 'नियम और गोपनीयता देखें', + burmese: 'စည်းမျဉ်းများနှင့် ကိုယ်ရေးလုံခြုံမှုကို ကြည့်ရှုပါ', + thai: 'ดูข้อกำหนดและความเป็นส่วนตัว', + mandarin: '查看条款和隐私' }, termsRequired: { english: 'You must agree to the Terms and Privacy', @@ -1741,7 +2659,11 @@ export const localizations = { brazilian_portuguese: 'Você deve concordar com os Termos e Privacidade', tok_pisin: 'Yu mas agri long Terms na Privacy', indonesian: 'Anda harus menyetujui Syarat dan Privasi', - nepali: 'तपाईंले सर्तहरू र गोपनीयता स्वीकार गर्नुपर्छ' + nepali: 'तपाईंले सर्तहरू र गोपनीयता स्वीकार गर्नुपर्छ', + hindi: 'आपको नियम और गोपनीयता से सहमत होना होगा', + burmese: 'သင်သည် စည်းမျဉ်းများနှင့် ကိုယ်ရေးလုံခြုံမှုကို သဘောတူရမည်', + thai: 'คุณต้องยอมรับข้อกำหนดและความเป็นส่วนตัว', + mandarin: '您必须同意条款和隐私' }, processing: { english: 'Processing...', @@ -1749,21 +2671,32 @@ export const localizations = { brazilian_portuguese: 'Processando...', tok_pisin: 'Processing...', indonesian: 'Memproses...', - nepali: 'प्रशोधन हुँदैछ...' + nepali: 'प्रशोधन हुँदैछ...', + hindi: 'प्रसंस्करण हो रहा है...', + burmese: 'လုပ်ဆောင်နေသည်...', + thai: 'กำลังประมวลผล...', + mandarin: '正在处理...' }, termsContributionInfo: { english: - 'By accepting these terms, you agree that all content you contribute to LangQuest will be freely available worldwide under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.', + 'By tapping {iAgree}, you agree that all content you contribute to LangQuest will be freely available worldwide under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.', spanish: - 'Al aceptar estos términos, acepta que todo el contenido que aporte a LangQuest estará disponible gratuitamente en todo el mundo bajo la Dedicación de Dominio Público CC0 1.0 Universal (CC0 1.0).', + 'Al tocar {iAgree}, acepta que todo el contenido que aporte a LangQuest estará disponible gratuitamente en todo el mundo bajo la Dedicación de Dominio Público CC0 1.0 Universal (CC0 1.0).', brazilian_portuguese: - 'Ao aceitar estes termos, você concorda que todo o conteúdo que você contribuir para o LangQuest estará disponível gratuitamente em todo o mundo sob a Dedicação ao Domínio Público CC0 1.0 Universal (CC0 1.0).', + 'Ao tocar {iAgree}, você concorda que todo o conteúdo que você contribuir para o LangQuest estará disponível gratuitamente em todo o mundo sob a Dedicação ao Domínio Público CC0 1.0 Universal (CC0 1.0).', tok_pisin: - 'Long akseptim ol dispela terms, yu agri long olgeta content yu contributim long LangQuest bai stap fri long olgeta hap long CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.', + 'Long paitim {iAgree}, yu agri long olgeta content yu contributim long LangQuest bai stap fri long olgeta hap long CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.', indonesian: - 'Dengan menerima syarat ini, Anda setuju bahwa semua konten yang Anda kontribusikan ke LangQuest akan tersedia secara gratis di seluruh dunia di bawah CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.', + 'Dengan mengetuk {iAgree}, Anda setuju bahwa semua konten yang Anda kontribusikan ke LangQuest akan tersedia secara gratis di seluruh dunia di bawah CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.', nepali: - 'यी सर्तहरू स्वीकार गरेर, तपाईं सहमत हुनुहुन्छ कि तपाईंले LangQuest मा योगदान गर्ने सबै सामग्री CC0 1.0 Universal (CC0 1.0) Public Domain Dedication अन्तर्गत विश्वव्यापी रूपमा नि:शुल्क उपलब्ध हुनेछ।' + '{iAgree} थिचेर, तपाईं सहमत हुनुहुन्छ कि तपाईंले LangQuest मा योगदान गर्ने सबै सामग्री CC0 1.0 Universal (CC0 1.0) Public Domain Dedication अन्तर्गत विश्वव्यापी रूपमा नि:शुल्क उपलब्ध हुनेछ।', + hindi: + '{iAgree} टैप करके, आप सहमत हैं कि LangQuest में आपके द्वारा योगदान की गई सभी सामग्री CC0 1.0 Universal (CC0 1.0) Public Domain Dedication के तहत दुनिया भर में स्वतंत्र रूप से उपलब्ध होगी।', + burmese: + '{iAgree} ကို နှိပ်ခြင်းဖြင့်၊ သင်သည် LangQuest သို့ ပံ့ပိုးသော အကြောင်းအရာအားလုံးသည် CC0 1.0 Universal (CC0 1.0) Public Domain Dedication အောက်တွင် ကမ္ဘာတစ်ဝှမ်းလုံး လွတ်လပ်စွာ ရရှိနိုင်မည်ဟု သဘောတူပါသည်။', + thai: 'โดยการแตะ{iAgree} คุณยอมรับว่าสื่อสารทั้งหมดที่คุณมีส่วนร่วมใน LangQuest จะสามารถใช้ได้ฟรีทั่วโลกภายใต้ CC0 1.0 Universal (CC0 1.0) Public Domain Dedication', + mandarin: + '通过点击{iAgree},您同意您为 LangQuest 贡献的所有内容将在 CC0 1.0 Universal (CC0 1.0) Public Domain Dedication 下在全球范围内免费提供。' }, termsDataInfo: { english: @@ -1777,7 +2710,14 @@ export const localizations = { indonesian: 'Ini berarti kontribusi Anda dapat digunakan oleh siapa saja untuk tujuan apa pun tanpa atribusi. Kami mengumpulkan data pengguna minimal: hanya email Anda (untuk pemulihan akun) dan langganan newsletter jika dipilih.', nepali: - 'यसको मतलब तपाईंको योगदान कुनै पनि व्यक्तिले कुनै पनि उद्देश्यको लागि श्रेय बिना प्रयोग गर्न सक्छ। हामी न्यूनतम प्रयोगकर्ता डाटा सङ्कलन गर्छौं: तपाईंको इमेल मात्र (खाता पुनर्प्राप्तिको लागि) र न्यूजलेटर सदस्यता यदि रोजिएको छ भने।' + 'यसको मतलब तपाईंको योगदान कुनै पनि व्यक्तिले कुनै पनि उद्देश्यको लागि श्रेय बिना प्रयोग गर्न सक्छ। हामी न्यूनतम प्रयोगकर्ता डाटा सङ्कलन गर्छौं: तपाईंको इमेल मात्र (खाता पुनर्प्राप्तिको लागि) र न्यूजलेटर सदस्यता यदि रोजिएको छ भने।', + hindi: + 'इसका मतलब है कि आपके योगदान का उपयोग कोई भी किसी भी उद्देश्य के लिए श्रेय के बिना कर सकता है। हम न्यूनतम उपयोगकर्ता डेटा एकत्र करते हैं: केवल आपका ईमेल (खाता पुनर्प्राप्ति के लिए) और न्यूज़लेटर सदस्यता यदि चुनी गई है।', + burmese: + '၎င်းသည် သင်၏ ပံ့ပိုးမှုများကို မည်သူမဆို မည်သည့်ရည်ရွယ်ချက်အတွက်မဆို အာတ်ထရီဘျူးရှင်းမပါဘဲ အသုံးပြုနိုင်သည်ဟု ဆိုလိုသည်။ ကျွန်ုပ်တို့သည် အနည်းဆုံး အသုံးပြုသူဒေတာကို စုဆောင်းပါသည်: သင်၏ အီးမေးလ် (အကောင့် ပြန်လည်ရယူရန်) နှင့် သတင်းလွှာစာရင်းသွင်းခြင်း (ရွေးချယ်ထားပါက)။', + thai: 'นี่หมายความว่าการมีส่วนร่วมของคุณสามารถใช้โดยใครก็ได้เพื่อวัตถุประสงค์ใดๆ โดยไม่ต้องระบุแหล่งที่มา เรารวบรวมข้อมูลผู้ใช้ขั้นต่ำ: เฉพาะอีเมลของคุณ (สำหรับการกู้คืนบัญชี) และการสมัครรับจดหมายข่าวหากเลือก', + mandarin: + '这意味着您的贡献可以被任何人用于任何目的,无需署名。我们收集最少的用户数据:仅您的电子邮件(用于账户恢复)和新闻订阅(如果选择)。' }, analyticsInfo: { english: @@ -1791,7 +2731,14 @@ export const localizations = { indonesian: 'Kami mengumpulkan data analitik dan diagnostik untuk meningkatkan aplikasi dan pengalaman Anda. Anda dapat memilih keluar dari analitik kapan saja di pengaturan profil Anda. Data Anda diproses dan disimpan di Amerika Serikat.', nepali: - 'हामी एप र तपाईंको अनुभव सुधार गर्न विश्लेषण र निदान डाटा सङ्कलन गर्छौं। तपाईं आफ्नो प्रोफाइल सेटिङहरूमा जुनसुकै समय विश्लेषणबाट अप्ट आउट गर्न सक्नुहुन्छ। तपाईंको डाटा संयुक्त राज्य अमेरिकामा प्रशोधन र भण्डारण गरिन्छ।' + 'हामी एप र तपाईंको अनुभव सुधार गर्न विश्लेषण र निदान डाटा सङ्कलन गर्छौं। तपाईं आफ्नो प्रोफाइल सेटिङहरूमा जुनसुकै समय विश्लेषणबाट अप्ट आउट गर्न सक्नुहुन्छ। तपाईंको डाटा संयुक्त राज्य अमेरिकामा प्रशोधन र भण्डारण गरिन्छ।', + hindi: + 'हम एप और आपके अनुभव को बेहतर बनाने के लिए विश्लेषण और नैदानिक डेटा एकत्र करते हैं। आप अपनी प्रोफ़ाइल सेटिंग्स में किसी भी समय विश्लेषण से बाहर निकल सकते हैं। आपका डेटा संयुक्त राज्य अमेरिका में संसाधित और संग्रहीत किया जाता है।', + burmese: + 'ကျွန်ုပ်တို့သည် အပ်ကို နှင့် သင်၏ အတွေ့အကြုံကို မြှင့်တင်ရန် ခွဲခြမ်းစိတ်ဖြာမှုနှင့် ရောဂါရှာဖွေရေး ဒေတာများကို စုဆောင်းပါသည်။ သင်သည် သင်၏ ကိုယ်ရေးအကျဉ်း ဆက်တင်များတွင် မည်သည့်အချိန်တွင်မဆို ခွဲခြမ်းစိတ်ဖြာမှုမှ ထွက်နိုင်သည်။ သင်၏ ဒေတာကို အမေရိကန်ပြည်ထောင်စုတွင် လုပ်ဆောင်ပြီး သိမ်းဆည်းပါသည်။', + thai: 'เรารวบรวมข้อมูลการวิเคราะห์และการวินิจฉัยเพื่อปรับปรุงแอปและประสบการณ์ของคุณ คุณสามารถเลือกไม่ใช้การวิเคราะห์ได้ตลอดเวลาในการตั้งค่าโปรไฟล์ของคุณ ข้อมูลของคุณถูกประมวลผลและจัดเก็บในสหรัฐอเมริกา', + mandarin: + '我们收集分析和诊断数据以改进应用程序和您的体验。您可以随时在个人资料设置中选择退出分析。您的数据在美国处理和存储。' }, viewFullTerms: { english: 'View Full Terms', @@ -1799,7 +2746,11 @@ export const localizations = { brazilian_portuguese: 'Ver Termos Completos', tok_pisin: 'Lukim Olgeta Terms', indonesian: 'Lihat Syarat Lengkap', - nepali: 'पूर्ण सर्तहरू हेर्नुहोस्' + nepali: 'पूर्ण सर्तहरू हेर्नुहोस्', + hindi: 'पूर्ण नियम देखें', + burmese: 'စည်းမျဉ်းအားလုံးကို ကြည့်ရှုပါ', + thai: 'ดูข้อกำหนดฉบับเต็ม', + mandarin: '查看完整条款' }, viewFullPrivacy: { english: 'View Full Privacy', @@ -1807,7 +2758,11 @@ export const localizations = { brazilian_portuguese: 'Ver Privacidade Completa', tok_pisin: 'Lukim Olgeta Privacy', indonesian: 'Lihat Privasi Lengkap', - nepali: 'पूर्ण गोपनीयता हेर्नुहोस्' + nepali: 'पूर्ण गोपनीयता हेर्नुहोस्', + hindi: 'पूर्ण गोपनीयता देखें', + burmese: 'ကိုယ်ရေးလုံခြုံမှု အားလုံးကို ကြည့်ရှုပါ', + thai: 'ดูความเป็นส่วนตัวฉบับเต็ม', + mandarin: '查看完整隐私' }, submitFeedback: { english: 'Submit Feedback', @@ -1815,25 +2770,47 @@ export const localizations = { brazilian_portuguese: 'Enviar Feedback', tok_pisin: 'Salim Feedback', indonesian: 'Kirim Umpan Balik', - nepali: 'प्रतिक्रिया पेश गर्नुहोस्' + nepali: 'प्रतिक्रिया पेश गर्नुहोस्', + hindi: 'प्रतिक्रिया जमा करें', + burmese: 'အကြံပြုချက်ကို တင်သွင်းပါ', + thai: 'ส่งข้อเสนอแนะ', + mandarin: '提交反馈' }, reportProject: { english: 'Report Project', spanish: 'Reportar Proyecto', brazilian_portuguese: 'Reportar Projeto', - nepali: 'प्रोजेक्ट रिपोर्ट गर्नुहोस्' + tok_pisin: 'Reportim Projek', + indonesian: 'Laporkan Proyek', + nepali: 'प्रोजेक्ट रिपोर्ट गर्नुहोस्', + hindi: 'परियोजना रिपोर्ट करें', + burmese: 'စီမံကိန်းကို သတင်းပို့ပါ', + thai: 'รายงานโครงการ', + mandarin: '报告项目' }, reportQuest: { english: 'Report Quest', spanish: 'Reportar Quest', brazilian_portuguese: 'Reportar Quest', - nepali: 'क्वेस्ट रिपोर्ट गर्नुहोस्' + tok_pisin: 'Reportim Quest', + indonesian: 'Laporkan Misi', + nepali: 'क्वेस्ट रिपोर्ट गर्नुहोस्', + hindi: 'क्वेस्ट रिपोर्ट करें', + burmese: 'Quest ကို သတင်းပို့ပါ', + thai: 'รายงานเควสต์', + mandarin: '报告任务' }, reportAsset: { english: 'Report Asset', spanish: 'Reportar Recurso', brazilian_portuguese: 'Reportar Recurso', - nepali: 'एसेट रिपोर्ट गर्नुहोस्' + tok_pisin: 'Reportim Asset', + indonesian: 'Laporkan Aset', + nepali: 'एसेट रिपोर्ट गर्नुहोस्', + hindi: 'एसेट रिपोर्ट करें', + burmese: 'ပိုင်ဆိုင်မှုကို သတင်းပို့ပါ', + thai: 'รายงานสินทรัพย์', + mandarin: '报告资产' }, reportTranslation: { english: 'Report Translation', @@ -1841,13 +2818,23 @@ export const localizations = { brazilian_portuguese: 'Reportar Tradução', tok_pisin: 'Reportim Translation', indonesian: 'Laporkan Terjemahan', - nepali: 'अनुवाद रिपोर्ट गर्नुहोस्' + nepali: 'अनुवाद रिपोर्ट गर्नुहोस्', + hindi: 'अनुवाद रिपोर्ट करें', + burmese: 'ဘာသာပြန်ဆိုချက်ကို သတင်းပို့ပါ', + thai: 'รายงานคำแปล', + mandarin: '报告翻译' }, reportGeneric: { english: 'Report', spanish: 'Reportar', brazilian_portuguese: 'Reportar', - nepali: 'रिपोर्ट गर्नुहोस्' + tok_pisin: 'Reportim', + indonesian: 'Laporkan', + nepali: 'रिपोर्ट गर्नुहोस्', + hindi: 'रिपोर्ट करें', + burmese: 'သတင်းပို့ပါ', + thai: 'รายงาน', + mandarin: '报告' }, selectReasonLabel: { english: 'Select a reason', @@ -1855,7 +2842,11 @@ export const localizations = { brazilian_portuguese: 'Selecione um motivo', tok_pisin: 'Makim wanpela reson', indonesian: 'Pilih alasan', - nepali: 'एउटा कारण चयन गर्नुहोस्' + nepali: 'एउटा कारण चयन गर्नुहोस्', + hindi: 'एक कारण चुनें', + burmese: 'အကြောင်းပြချက်တစ်ခုကို ရွေးချယ်ပါ', + thai: 'เลือกเหตุผล', + mandarin: '选择原因' }, additionalDetails: { english: 'Additional Details', @@ -1863,7 +2854,11 @@ export const localizations = { brazilian_portuguese: 'Detalhes Adicionais', tok_pisin: 'Moa Details', indonesian: 'Detail Tambahan', - nepali: 'थप विवरणहरू' + nepali: 'थप विवरणहरू', + hindi: 'अतिरिक्त विवरण', + burmese: 'အပိုအသေးစိတ်များ', + thai: 'รายละเอียดเพิ่มเติม', + mandarin: '其他详细信息' }, additionalDetailsPlaceholder: { english: 'Provide any additional information...', @@ -1871,7 +2866,11 @@ export const localizations = { brazilian_portuguese: 'Forneça qualquer informação adicional...', tok_pisin: 'Givim narapela information...', indonesian: 'Berikan informasi tambahan...', - nepali: 'कुनै थप जानकारी प्रदान गर्नुहोस्...' + nepali: 'कुनै थप जानकारी प्रदान गर्नुहोस्...', + hindi: 'कोई अतिरिक्त जानकारी प्रदान करें...', + burmese: 'မည်သည့် အပို အချက်အလက်ကိုမဆို ပေးပါ...', + thai: 'ให้ข้อมูลเพิ่มเติมใดๆ...', + mandarin: '提供任何其他信息...' }, submitReport: { english: 'Submit Report', @@ -1879,7 +2878,11 @@ export const localizations = { brazilian_portuguese: 'Enviar Relatório', tok_pisin: 'Salim Report', indonesian: 'Kirim Laporan', - nepali: 'रिपोर्ट पेश गर्नुहोस्' + nepali: 'रिपोर्ट पेश गर्नुहोस्', + hindi: 'रिपोर्ट जमा करें', + burmese: 'သတင်းပို့ချက်ကို တင်သွင်းပါ', + thai: 'ส่งรายงาน', + mandarin: '提交报告' }, submitting: { english: 'Submitting...', @@ -1887,7 +2890,11 @@ export const localizations = { brazilian_portuguese: 'Enviando...', tok_pisin: 'Salim...', indonesian: 'Mengirim...', - nepali: 'पेश गर्दै...' + nepali: 'पेश गर्दै...', + hindi: 'जमा कर रहे हैं...', + burmese: 'တင်သွင်းနေသည်...', + thai: 'กำลังส่ง...', + mandarin: '正在提交...' }, reportSubmitted: { english: 'Report submitted successfully', @@ -1895,7 +2902,11 @@ export const localizations = { brazilian_portuguese: 'Relatório enviado com sucesso', tok_pisin: 'Report i go gut', indonesian: 'Laporan berhasil dikirim', - nepali: 'रिपोर्ट सफलतापूर्वक पेश गरियो' + nepali: 'रिपोर्ट सफलतापूर्वक पेश गरियो', + hindi: 'रिपोर्ट सफलतापूर्वक जमा हो गई', + burmese: 'သတင်းပို့ချက်ကို အောင်မြင်စွာ တင်သွင်းပြီးပါပြီ', + thai: 'ส่งรายงานสำเร็จ', + mandarin: '报告提交成功' }, enterEmailForPasswordReset: { english: 'Enter your email to reset your password', @@ -1903,7 +2914,11 @@ export const localizations = { brazilian_portuguese: 'Digite seu e-mail para redefinir sua senha', tok_pisin: 'Putim email bilong yu long resetim password', indonesian: 'Masukkan email Anda untuk mereset kata sandi', - nepali: 'पासवर्ड रिसेट गर्न आफ्नो इमेल प्रविष्ट गर्नुहोस्' + nepali: 'पासवर्ड रिसेट गर्न आफ्नो इमेल प्रविष्ट गर्नुहोस्', + hindi: 'अपना पासवर्ड रीसेट करने के लिए अपना ईमेल दर्ज करें', + burmese: 'သင်၏ စကားဝှက်ကို ပြန်လည်သတ်မှတ်ရန် သင်၏ အီးမေးလ်ကို ထည့်သွင်းပါ', + thai: 'ป้อนอีเมลของคุณเพื่อรีเซ็ตรหัสผ่าน', + mandarin: '输入您的电子邮件以重置密码' }, failedToSubmitReport: { english: 'Failed to submit report', @@ -1911,7 +2926,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao enviar relatório', tok_pisin: 'I no inap salim report', indonesian: 'Gagal mengirim laporan', - nepali: 'रिपोर्ट पेश गर्न असफल' + nepali: 'रिपोर्ट पेश गर्न असफल', + hindi: 'रिपोर्ट जमा करने में विफल', + burmese: 'သတင်းပို့ချက်ကို တင်သွင်း၍မရပါ', + thai: 'ส่งรายงานไม่สำเร็จ', + mandarin: '提交报告失败' }, logInToReport: { english: 'You must be logged in to report translations', @@ -1919,7 +2938,11 @@ export const localizations = { brazilian_portuguese: 'Você deve estar logado para reportar traduções', tok_pisin: 'Yu mas login pastaim long reportim ol translation', indonesian: 'Anda harus masuk untuk melaporkan terjemahan', - nepali: 'अनुवादहरू रिपोर्ट गर्न तपाईं लग इन हुनुपर्छ' + nepali: 'अनुवादहरू रिपोर्ट गर्न तपाईं लग इन हुनुपर्छ', + hindi: 'अनुवाद रिपोर्ट करने के लिए आपको लॉग इन होना होगा', + burmese: 'ဘာသာပြန်ဆိုချက်များကို သတင်းပို့ရန် သင်သည် အကောင့်ဝင်ရမည်', + thai: 'คุณต้องเข้าสู่ระบบเพื่อรายงานคำแปล', + mandarin: '您必须登录才能报告翻译' }, selectReason: { english: 'Please select a reason for the report', @@ -1927,7 +2950,11 @@ export const localizations = { brazilian_portuguese: 'Por favor, selecione um motivo para o relatório', tok_pisin: 'Plis makim wanpela reson long report', indonesian: 'Silakan pilih alasan untuk laporan', - nepali: 'कृपया रिपोर्टको लागि एउटा कारण चयन गर्नुहोस्' + nepali: 'कृपया रिपोर्टको लागि एउटा कारण चयन गर्नुहोस्', + hindi: 'कृपया रिपोर्ट के लिए एक कारण चुनें', + burmese: 'ကျေးဇူးပြု၍ သတင်းပို့ချက်အတွက် အကြောင်းပြချက်တစ်ခုကို ရွေးချယ်ပါ', + thai: 'กรุณาเลือกเหตุผลสำหรับรายงาน', + mandarin: '请选择报告原因' }, enableAnalytics: { english: 'Enable Analytics', @@ -1935,7 +2962,11 @@ export const localizations = { brazilian_portuguese: 'Habilitar Análise', tok_pisin: 'Onim Analytics', indonesian: 'Aktifkan Analitik', - nepali: 'विश्लेषण सक्षम गर्नुहोस्' + nepali: 'विश्लेषण सक्षम गर्नुहोस्', + hindi: 'विश्लेषण सक्षम करें', + burmese: 'ခွဲခြမ်းစိတ်ဖြာမှုကို ဖွင့်ပါ', + thai: 'เปิดใช้งานการวิเคราะห์', + mandarin: '启用分析' }, analyticsDescription: { english: @@ -1949,7 +2980,13 @@ export const localizations = { indonesian: 'Ketika dinonaktifkan, kami tidak akan mengumpulkan data penggunaan untuk meningkatkan aplikasi.', nepali: - 'असक्षम गरिएको बेला, हामी एप सुधार गर्न प्रयोग डाटा सङ्कलन गर्ने छैनौं।' + 'असक्षम गरिएको बेला, हामी एप सुधार गर्न प्रयोग डाटा सङ्कलन गर्ने छैनौं।', + hindi: + 'अक्षम होने पर, हम एप को बेहतर बनाने के लिए उपयोग डेटा एकत्र नहीं करेंगे।', + burmese: + 'ပိတ်ထားသောအခါ၊ ကျွန်ုပ်တို့သည် အပ်ကို မြှင့်တင်ရန် အသုံးပြုမှု ဒေတာကို စုဆောင်းမည်မဟုတ်ပါ။', + thai: 'เมื่อปิดใช้งาน เราจะไม่รวบรวมข้อมูลการใช้งานเพื่อปรับปรุงแอป', + mandarin: '禁用后,我们将不会收集使用数据来改进应用程序。' }, sessionExpired: { english: 'Session expired', @@ -1957,7 +2994,11 @@ export const localizations = { brazilian_portuguese: 'Sessão expirada', tok_pisin: 'Session i pinis', indonesian: 'Sesi kedaluwarsa', - nepali: 'सत्र समाप्त भयो' + nepali: 'सत्र समाप्त भयो', + hindi: 'सत्र समाप्त हो गया', + burmese: 'ဆက်ရှင်သက်တမ်းကုန်ဆုံးပါပြီ', + thai: 'เซสชันหมดอายุ', + mandarin: '会话已过期' }, 'reportReason.inappropriate_content': { english: 'Inappropriate Content', @@ -1965,7 +3006,11 @@ export const localizations = { brazilian_portuguese: 'Conteúdo Inapropriado', tok_pisin: 'Content i no gutpela', indonesian: 'Konten Tidak Pantas', - nepali: 'अनुचित सामग्री' + nepali: 'अनुचित सामग्री', + hindi: 'अनुचित सामग्री', + burmese: 'မသင့်လျော်သော အကြောင်းအရာ', + thai: 'เนื้อหาไม่เหมาะสม', + mandarin: '不当内容' }, 'reportReason.spam': { english: 'Spam', @@ -1973,7 +3018,11 @@ export const localizations = { brazilian_portuguese: 'Spam', tok_pisin: 'Spam', indonesian: 'Spam', - nepali: 'स्प्याम' + nepali: 'स्प्याम', + hindi: 'स्पैम', + burmese: 'အမှိုက်စာ', + thai: 'สแปม', + mandarin: '垃圾信息' }, 'reportReason.other': { english: 'Other', @@ -1981,7 +3030,11 @@ export const localizations = { brazilian_portuguese: 'Outro', tok_pisin: 'Narapela', indonesian: 'Lainnya', - nepali: 'अन्य' + nepali: 'अन्य', + hindi: 'अन्य', + burmese: 'အခြား', + thai: 'อื่นๆ', + mandarin: '其他' }, updatePassword: { english: 'Update Password', @@ -1989,7 +3042,11 @@ export const localizations = { brazilian_portuguese: 'Atualizar Senha', tok_pisin: 'Updateim Password', indonesian: 'Perbarui Kata Sandi', - nepali: 'पासवर्ड अपडेट गर्नुहोस्' + nepali: 'पासवर्ड अपडेट गर्नुहोस्', + hindi: 'पासवर्ड अपडेट करें', + burmese: 'စကားဝှက်ကို အပ်ဒိတ်လုပ်ပါ', + thai: 'อัปเดตรหัสผ่าน', + mandarin: '更新密码' }, createNewPassword: { english: 'Create New Password', @@ -1997,7 +3054,11 @@ export const localizations = { brazilian_portuguese: 'Criar nova senha', tok_pisin: 'Mekim nupela password', indonesian: 'Buat Kata Sandi Baru', - nepali: 'नयाँ पासवर्ड सिर्जना गर्नुहोस्' + nepali: 'नयाँ पासवर्ड सिर्जना गर्नुहोस्', + hindi: 'नया पासवर्ड बनाएं', + burmese: 'စကားဝှက် အသစ် ဖန်တီးပါ', + thai: 'สร้างรหัสผ่านใหม่', + mandarin: '创建新密码' }, downloadLimitExceeded: { english: 'Download Limit Exceeded', @@ -2005,7 +3066,11 @@ export const localizations = { brazilian_portuguese: 'Limite de download excedido', tok_pisin: 'Download limit i pinis', indonesian: 'Batas Unduhan Terlampaui', - nepali: 'डाउनलोड सीमा नाघ्यो' + nepali: 'डाउनलोड सीमा नाघ्यो', + hindi: 'डाउनलोड सीमा पार हो गई', + burmese: 'ဒေါင်းလုဒ် ကန့်သတ်ချက် ကျော်လွန်သွားပါပြီ', + thai: 'เกินขีดจำกัดการดาวน์โหลด', + mandarin: '超过下载限制' }, downloadLimitMessage: { english: @@ -2019,7 +3084,14 @@ export const localizations = { indonesian: 'Anda mencoba mengunduh {newDownloads} lampiran untuk total {totalDownloads}, tetapi batasnya adalah {limit}. Silakan batalkan pilihan beberapa unduhan dan coba lagi.', nepali: - 'तपाईं {totalDownloads} को कुल लागि {newDownloads} संलग्नकहरू डाउनलोड गर्न प्रयास गर्दै हुनुहुन्छ, तर सीमा {limit} हो। कृपया केही डाउनलोडहरू अचयन गर्नुहोस् र पुन: प्रयास गर्नुहोस्।' + 'तपाईं {totalDownloads} को कुल लागि {newDownloads} संलग्नकहरू डाउनलोड गर्न प्रयास गर्दै हुनुहुन्छ, तर सीमा {limit} हो। कृपया केही डाउनलोडहरू अचयन गर्नुहोस् र पुन: प्रयास गर्नुहोस्।', + hindi: + 'आप कुल {totalDownloads} के लिए {newDownloads} संलग्नक डाउनलोड करने का प्रयास कर रहे हैं, लेकिन सीमा {limit} है। कृपया कुछ डाउनलोड अचयन करें और पुनः प्रयास करें।', + burmese: + 'သင်သည် စုစုပေါင်း {totalDownloads} အတွက် {newDownloads} ပူးတွဲဖိုင်များကို ဒေါင်းလုဒ်လုပ်ရန် ကြိုးစားနေပါသည်၊ သို့သော် ကန့်သတ်ချက်သည် {limit} ဖြစ်သည်။ ကျေးဇူးပြု၍ ဒေါင်းလုဒ်အချို့ကို ရွေးချယ်မှု ဖျက်ပြီး ထပ်မံကြိုးစားပါ။', + thai: 'คุณกำลังพยายามดาวน์โหลดไฟล์แนบ {newDownloads} รายการ รวมทั้งหมด {totalDownloads} แต่ขีดจำกัดคือ {limit} กรุณายกเลิกการเลือกดาวน์โหลดบางรายการและลองอีกครั้ง', + mandarin: + '您正在尝试下载 {newDownloads} 个附件,总计 {totalDownloads},但限制是 {limit}。请取消选择一些下载并重试。' }, offlineUndownloadWarning: { english: 'Offline Undownload Warning', @@ -2027,7 +3099,11 @@ export const localizations = { brazilian_portuguese: 'Aviso de remoção de download offline', tok_pisin: 'Offline Undownload Warning', indonesian: 'Peringatan Batalkan Unduhan Offline', - nepali: 'अफलाइन अनडाउनलोड चेतावनी' + nepali: 'अफलाइन अनडाउनलोड चेतावनी', + hindi: 'ऑफलाइन अनडाउनलोड चेतावनी', + burmese: 'အင်တာနက်မရှိသော ဒေါင်းလုဒ်ဖျက်ခြင်း သတိပေးချက်', + thai: 'คำเตือนการยกเลิกดาวน์โหลดแบบออฟไลน์', + mandarin: '离线取消下载警告' }, offlineUndownloadMessage: { english: @@ -2041,7 +3117,14 @@ export const localizations = { indonesian: 'Anda sedang offline. Jika Anda menghapus unduhan ini, Anda tidak akan dapat mengunduhnya lagi sampai Anda kembali online. Kontribusi yang belum disinkronkan tidak akan terpengaruh.', nepali: - 'तपाईं अहिले अफलाइन हुनुहुन्छ। यदि तपाईंले यो डाउनलोड हटाउनुभयो भने, तपाईं अनलाइन नभएसम्म पुन: डाउनलोड गर्न सक्षम हुनुहुने छैन। तपाईंको सिङ्क नभएका योगदानहरू प्रभावित हुने छैनन्।' + 'तपाईं अहिले अफलाइन हुनुहुन्छ। यदि तपाईंले यो डाउनलोड हटाउनुभयो भने, तपाईं अनलाइन नभएसम्म पुन: डाउनलोड गर्न सक्षम हुनुहुने छैन। तपाईंको सिङ्क नभएका योगदानहरू प्रभावित हुने छैनन्।', + hindi: + 'आप वर्तमान में ऑफलाइन हैं। यदि आप इस डाउनलोड को हटाते हैं, तो आप ऑनलाइन वापस आने तक इसे पुनः डाउनलोड नहीं कर सकेंगे। आपके असंगत योगदान प्रभावित नहीं होंगे।', + burmese: + 'သင်သည် လက်ရှိတွင် အင်တာနက်မရှိပါ။ သင်သည် ဤဒေါင်းလုဒ်ကို ဖျက်ပါက၊ သင်သည် အင်တာနက်ပြန်ရသည့်အထိ ထပ်မံဒေါင်းလုဒ်လုပ်၍မရပါ။ သင်၏ စင့်ချန်မထားသော ပံ့ပိုးမှုများကို ထိခိုက်မည်မဟုတ်ပါ။', + thai: 'คุณกำลังออฟไลน์อยู่ หากคุณลบการดาวน์โหลดนี้ คุณจะไม่สามารถดาวน์โหลดอีกครั้งได้จนกว่าคุณจะกลับมาออนไลน์ การมีส่วนร่วมที่ยังไม่ได้ซิงค์ของคุณจะไม่ได้รับผลกระทบ', + mandarin: + '您目前处于离线状态。如果您删除此下载,在您重新上线之前将无法重新下载。您未同步的贡献不会受到影响。' }, dontShowAgain: { english: "Don't show this message again", @@ -2049,7 +3132,11 @@ export const localizations = { brazilian_portuguese: 'Não mostrar esta mensagem novamente', tok_pisin: 'No soim dispela message gen', indonesian: 'Jangan tampilkan pesan ini lagi', - nepali: 'यो सन्देश फेरि नदेखाउनुहोस्' + nepali: 'यो सन्देश फेरि नदेखाउनुहोस्', + hindi: 'इस संदेश को फिर से न दिखाएं', + burmese: 'ဤစာတိုကို ထပ်မံမပြပါနှင့်', + thai: 'ไม่แสดงข้อความนี้อีก', + mandarin: '不再显示此消息' }, cancel: { english: 'Cancel', @@ -2057,7 +3144,11 @@ export const localizations = { brazilian_portuguese: 'Cancelar', tok_pisin: 'Cancel', indonesian: 'Batal', - nepali: 'रद्द गर्नुहोस्' + nepali: 'रद्द गर्नुहोस्', + hindi: 'रद्द करें', + burmese: 'ပယ်ဖျက်ပါ', + thai: 'ยกเลิก', + mandarin: '取消' }, yes: { english: 'Yes', @@ -2065,7 +3156,11 @@ export const localizations = { brazilian_portuguese: 'Sim', tok_pisin: 'Yes', indonesian: 'Ya', - nepali: 'हो' + nepali: 'हो', + hindi: 'हाँ', + burmese: 'ဟုတ်ကဲ့', + thai: 'ใช่', + mandarin: '是' }, no: { english: 'No', @@ -2073,7 +3168,11 @@ export const localizations = { brazilian_portuguese: 'Não', tok_pisin: 'Nogat', indonesian: 'Tidak', - nepali: 'होइन' + nepali: 'होइन', + hindi: 'नहीं', + burmese: 'မဟုတ်ပါ', + thai: 'ไม่', + mandarin: '否' }, confirm: { english: 'Confirm', @@ -2081,7 +3180,11 @@ export const localizations = { brazilian_portuguese: 'Confirmar', tok_pisin: 'Confirm', indonesian: 'Konfirmasi', - nepali: 'पुष्टि गर्नुहोस्' + nepali: 'पुष्टि गर्नुहोस्', + hindi: 'पुष्टि करें', + burmese: 'အတည်ပြုပါ', + thai: 'ยืนยัน', + mandarin: '确认' }, blockThisContent: { english: 'Block this content', @@ -2089,7 +3192,11 @@ export const localizations = { brazilian_portuguese: 'Bloquear este conteúdo', tok_pisin: 'Blokim dispela content', indonesian: 'Blokir konten ini', - nepali: 'यो सामग्री ब्लक गर्नुहोस्' + nepali: 'यो सामग्री ब्लक गर्नुहोस्', + hindi: 'इस सामग्री को ब्लॉक करें', + burmese: 'ဤအကြောင်းအရာကို ပိတ်ဆို့ပါ', + thai: 'บล็อกเนื้อหานี้', + mandarin: '阻止此内容' }, blockThisUser: { english: 'Block this user', @@ -2097,7 +3204,11 @@ export const localizations = { brazilian_portuguese: 'Bloquear este usuário', tok_pisin: 'Blokim dispela user', indonesian: 'Blokir pengguna ini', - nepali: 'यो प्रयोगकर्ता ब्लक गर्नुहोस्' + nepali: 'यो प्रयोगकर्ता ब्लक गर्नुहोस्', + hindi: 'इस उपयोगकर्ता को ब्लॉक करें', + burmese: 'ဤအသုံးပြုသူကို ပိတ်ဆို့ပါ', + thai: 'บล็อกผู้ใช้นี้', + mandarin: '阻止此用户' }, // New backup-related translations backup: { @@ -2106,7 +3217,11 @@ export const localizations = { brazilian_portuguese: 'Backup', tok_pisin: 'Backup', indonesian: 'Cadangan', - nepali: 'ब्याकअप' + nepali: 'ब्याकअप', + hindi: 'बैकअप', + burmese: 'အရန်သိမ်းဆည်းမှု', + thai: 'สำรอง', + mandarin: '备份' }, backingUp: { english: 'Backing Up...', @@ -2114,7 +3229,11 @@ export const localizations = { brazilian_portuguese: 'Fazendo Backup...', tok_pisin: 'Backup...', indonesian: 'Mencadangkan...', - nepali: 'ब्याकअप गर्दै...' + nepali: 'ब्याकअप गर्दै...', + hindi: 'बैकअप हो रहा है...', + burmese: 'အရန်သိမ်းဆည်းနေသည်...', + thai: 'กำลังสำรอง...', + mandarin: '正在备份...' }, restoreBackup: { english: 'Restore Backup', @@ -2122,7 +3241,11 @@ export const localizations = { brazilian_portuguese: 'Restaurar Backup', tok_pisin: 'Restore Backup', indonesian: 'Pulihkan Cadangan', - nepali: 'ब्याकअप पुनर्स्थापना गर्नुहोस्' + nepali: 'ब्याकअप पुनर्स्थापना गर्नुहोस्', + hindi: 'बैकअप पुनर्स्थापित करें', + burmese: 'အရန်သိမ်းဆည်းမှုကို ပြန်လည်ထားရှိပါ', + thai: 'กู้คืนการสำรอง', + mandarin: '恢复备份' }, restoring: { english: 'Restoring...', @@ -2130,7 +3253,11 @@ export const localizations = { brazilian_portuguese: 'Restaurando...', tok_pisin: 'Restore...', indonesian: 'Memulihkan...', - nepali: 'पुनर्स्थापना गर्दै...' + nepali: 'पुनर्स्थापना गर्दै...', + hindi: 'पुनर्स्थापित हो रहा है...', + burmese: 'ပြန်လည်ထားရှိနေသည်...', + thai: 'กำลังกู้คืน...', + mandarin: '正在恢复...' }, startBackupTitle: { english: 'Create Backup', @@ -2138,7 +3265,11 @@ export const localizations = { brazilian_portuguese: 'Criar Backup', tok_pisin: 'Mekim Backup', indonesian: 'Buat Cadangan', - nepali: 'ब्याकअप सिर्जना गर्नुहोस्' + nepali: 'ब्याकअप सिर्जना गर्नुहोस्', + hindi: 'बैकअप बनाएं', + burmese: 'အရန်သိမ်းဆည်းမှု ဖန်တီးပါ', + thai: 'สร้างการสำรอง', + mandarin: '创建备份' }, startBackupMessageAudioOnly: { english: 'Would you like to back up your unsynced audio recordings?', @@ -2150,7 +3281,12 @@ export const localizations = { indonesian: 'Apakah Anda ingin mencadangkan rekaman audio yang belum disinkronkan?', nepali: - 'के तपाईं आफ्नो सिङ्क नभएका अडियो रेकर्डिङहरू ब्याकअप गर्न चाहनुहुन्छ?' + 'के तपाईं आफ्नो सिङ्क नभएका अडियो रेकर्डिङहरू ब्याकअप गर्न चाहनुहुन्छ?', + hindi: 'क्या आप अपने असंगत ऑडियो रिकॉर्डिंग का बैकअप लेना चाहेंगे?', + burmese: + 'သင်သည် သင်၏ စင့်ချန်မထားသော အသံမှတ်တမ်းများကို အရန်သိမ်းဆည်းလိုပါသလား?', + thai: 'คุณต้องการสำรองการบันทึกเสียงที่ยังไม่ได้ซิงค์ของคุณหรือไม่?', + mandarin: '您想备份未同步的音频录音吗?' }, backupAudioAction: { english: 'Backup audio and text', @@ -2158,7 +3294,11 @@ export const localizations = { brazilian_portuguese: 'Backup de áudio e texto', tok_pisin: 'Backup audio na text', indonesian: 'Cadangkan audio dan teks', - nepali: 'अडियो र टेक्स्ट ब्याकअप गर्नुहोस्' + nepali: 'अडियो र टेक्स्ट ब्याकअप गर्नुहोस्', + hindi: 'ऑडियो और टेक्स्ट का बैकअप लें', + burmese: 'အသံနှင့် စာသားကို အရန်သိမ်းဆည်းပါ', + thai: 'สำรองเสียงและข้อความ', + mandarin: '备份音频和文本' }, backupErrorTitle: { english: 'Backup Error', @@ -2166,7 +3306,11 @@ export const localizations = { brazilian_portuguese: 'Erro de Backup', tok_pisin: 'Backup Rong', indonesian: 'Kesalahan Cadangan', - nepali: 'ब्याकअप त्रुटि' + nepali: 'ब्याकअप त्रुटि', + hindi: 'बैकअप त्रुटि', + burmese: 'အရန်သိမ်းဆည်းမှု အမှား', + thai: 'ข้อผิดพลาดในการสำรอง', + mandarin: '备份错误' }, backupCompleteTitle: { english: 'Backup Complete', @@ -2174,7 +3318,11 @@ export const localizations = { brazilian_portuguese: 'Backup Concluído', tok_pisin: 'Backup Pinis', indonesian: 'Cadangan Selesai', - nepali: 'ब्याकअप पूरा भयो' + nepali: 'ब्याकअप पूरा भयो', + hindi: 'बैकअप पूर्ण', + burmese: 'အရန်သိမ်းဆည်းမှု ပြီးစီးပါပြီ', + thai: 'สำรองเสร็จสมบูรณ์', + mandarin: '备份完成' }, audioBackupStatus: { english: 'Successfully backed up {count} audio recordings', @@ -2183,7 +3331,11 @@ export const localizations = { 'Backup de {count} gravações de áudio concluído com sucesso', tok_pisin: 'Backup {count} audio recordings gut', indonesian: 'Berhasil mencadangkan {count} rekaman audio', - nepali: '{count} अडियो रेकर्डिङहरू सफलतापूर्वक ब्याकअप गरियो' + nepali: '{count} अडियो रेकर्डिङहरू सफलतापूर्वक ब्याकअप गरियो', + hindi: '{count} ऑडियो रिकॉर्डिंग सफलतापूर्वक बैकअप हो गई', + burmese: '{count} အသံမှတ်တမ်းများကို အောင်မြင်စွာ အရန်သိမ်းဆည်းပြီးပါပြီ', + thai: 'สำรองการบันทึกเสียง {count} รายการสำเร็จ', + mandarin: '已成功备份 {count} 个音频录音' }, criticalBackupError: { english: 'A critical error occurred: {error}', @@ -2191,7 +3343,11 @@ export const localizations = { brazilian_portuguese: 'Ocorreu um erro crítico: {error}', tok_pisin: 'Bikpela rong i kamap: {error}', indonesian: 'Terjadi kesalahan kritis: {error}', - nepali: 'एउटा गम्भीर त्रुटि भयो: {error}' + nepali: 'एउटा गम्भीर त्रुटि भयो: {error}', + hindi: 'एक गंभीर त्रुटि हुई: {error}', + burmese: 'အရေးကြီးသော အမှားတစ်ခု ဖြစ်ပွားခဲ့သည်: {error}', + thai: 'เกิดข้อผิดพลาดร้ายแรง: {error}', + mandarin: '发生严重错误: {error}' }, databaseNotReady: { english: 'Database is not ready. Please try again later.', @@ -2200,7 +3356,12 @@ export const localizations = { 'O banco de dados não está pronto. Por favor, tente novamente mais tarde.', tok_pisin: 'Database i no redi yet. Plis traim gen bihain.', indonesian: 'Database belum siap. Silakan coba lagi nanti.', - nepali: 'डाटाबेस तयार छैन। कृपया पछि पुन: प्रयास गर्नुहोस्।' + nepali: 'डाटाबेस तयार छैन। कृपया पछि पुन: प्रयास गर्नुहोस्।', + hindi: 'डेटाबेस तैयार नहीं है। कृपया बाद में पुनः प्रयास करें।', + burmese: + 'ဒေတာဘေ့စ်သည် အဆင်သင့်မဖြစ်သေးပါ။ ကျေးဇူးပြု၍ နောက်ပိုင်းတွင် ထပ်မံကြိုးစားပါ။', + thai: 'ฐานข้อมูลยังไม่พร้อม กรุณาลองอีกครั้งในภายหลัง', + mandarin: '数据库尚未准备好。请稍后再试。' }, storagePermissionDenied: { english: 'Storage permission denied. Backup cannot proceed.', @@ -2210,7 +3371,12 @@ export const localizations = { 'Permissão de armazenamento negada. O backup não pode prosseguir.', tok_pisin: 'Storage permission i no. Backup i no inap go.', indonesian: 'Izin penyimpanan ditolak. Cadangan tidak dapat dilanjutkan.', - nepali: 'भण्डारण अनुमति अस्वीकृत। ब्याकअप अगाडि बढ्न सक्दैन।' + nepali: 'भण्डारण अनुमति अस्वीकृत। ब्याकअप अगाडि बढ्न सक्दैन।', + hindi: 'भंडारण अनुमति अस्वीकृत। बैकअप आगे नहीं बढ़ सकता।', + burmese: + 'သိုလှောင်မှု ခွင့်ပြုချက်ကို ငြင်းဆိုပါသည်။ အရန်သိမ်းဆည်းမှု ဆက်လုပ်မရပါ။', + thai: 'ปฏิเสธการอนุญาตการจัดเก็บ ไม่สามารถดำเนินการสำรองได้', + mandarin: '存储权限被拒绝。备份无法继续。' }, // Adding missing translation keys initializing: { @@ -2219,7 +3385,11 @@ export const localizations = { brazilian_portuguese: 'Inicializando', tok_pisin: 'Initializing', indonesian: 'Menginisialisasi', - nepali: 'सुरुवात गर्दै' + nepali: 'सुरुवात गर्दै', + hindi: 'आरंभ कर रहा है', + burmese: 'စတင်နေသည်', + thai: 'กำลังเริ่มต้น', + mandarin: '正在初始化' }, syncComplete: { english: 'Sync complete', @@ -2227,7 +3397,11 @@ export const localizations = { brazilian_portuguese: 'Sincronização completa', nepali: 'सिङ्क पूरा भयो', tok_pisin: 'Sync pinis', - indonesian: 'Sinkronisasi selesai' + indonesian: 'Sinkronisasi selesai', + hindi: 'सिंक पूर्ण', + burmese: 'စင့်ချန်မှု ပြီးစီးပါပြီ', + thai: 'ซิงค์เสร็จสมบูรณ์', + mandarin: '同步完成' }, syncProgress: { english: '{current} of {total} files', @@ -2235,7 +3409,11 @@ export const localizations = { brazilian_portuguese: '{current} de {total} arquivos', tok_pisin: '{current} long {total} files', indonesian: '{current} dari {total} file', - nepali: '{total} मध्ये {current} फाइलहरू' + nepali: '{total} मध्ये {current} फाइलहरू', + hindi: '{total} में से {current} फाइलें', + burmese: '{total} ဖိုင်များထဲမှ {current}', + thai: '{current} จาก {total} ไฟล์', + mandarin: '{total} 个文件中的 {current} 个' }, userNotLoggedIn: { english: 'You must be logged in to perform this action', @@ -2243,7 +3421,11 @@ export const localizations = { brazilian_portuguese: 'Você deve estar logado para realizar esta ação', tok_pisin: 'Yu mas login pastaim long mekim dispela samting', indonesian: 'Anda harus masuk untuk melakukan tindakan ini', - nepali: 'यो कार्य गर्न तपाईं लग इन हुनुपर्छ' + nepali: 'यो कार्य गर्न तपाईं लग इन हुनुपर्छ', + hindi: 'इस कार्रवाई को करने के लिए आपको लॉग इन होना होगा', + burmese: 'ဤလုပ်ဆောင်ချက်ကို လုပ်ဆောင်ရန် သင်သည် အကောင့်ဝင်ရမည်', + thai: 'คุณต้องเข้าสู่ระบบเพื่อดำเนินการนี้', + mandarin: '您必须登录才能执行此操作' }, cannotReportOwnTranslation: { english: 'You cannot report your own translation', @@ -2251,7 +3433,11 @@ export const localizations = { brazilian_portuguese: 'Você não pode reportar sua própria tradução', tok_pisin: 'Yu no inap reportim translation bilong yu yet', indonesian: 'Anda tidak dapat melaporkan terjemahan Anda sendiri', - nepali: 'तपाईं आफ्नो अनुवाद रिपोर्ट गर्न सक्नुहुन्न' + nepali: 'तपाईं आफ्नो अनुवाद रिपोर्ट गर्न सक्नुहुन्न', + hindi: 'आप अपना अनुवाद रिपोर्ट नहीं कर सकते', + burmese: 'သင်သည် သင်၏ ကိုယ်ပိုင် ဘာသာပြန်ဆိုချက်ကို သတင်းပို့၍မရပါ', + thai: 'คุณไม่สามารถรายงานคำแปลของคุณเองได้', + mandarin: '您不能报告自己的翻译' }, cannotReportInactiveTranslation: { english: 'You cannot report inactive translation', @@ -2259,7 +3445,11 @@ export const localizations = { brazilian_portuguese: 'Você não pode reportar tradução inativa', tok_pisin: 'Yu no inap reportim translation i no active', indonesian: 'Anda tidak dapat melaporkan terjemahan yang tidak aktif', - nepali: 'तपाईं निष्क्रिय अनुवाद रिपोर्ट गर्न सक्नुहुन्न' + nepali: 'तपाईं निष्क्रिय अनुवाद रिपोर्ट गर्न सक्नुहुन्न', + hindi: 'आप निष्क्रिय अनुवाद रिपोर्ट नहीं कर सकते', + burmese: 'သင်သည် မလှုပ်ရှားသော ဘာသာပြန်ဆိုချက်ကို သတင်းပို့၍မရပါ', + thai: 'คุณไม่สามารถรายงานคำแปลที่ไม่ใช้งานได้', + mandarin: '您不能报告非活动翻译' }, cannotIdentifyUser: { english: 'Unable to identify user', @@ -2267,7 +3457,11 @@ export const localizations = { brazilian_portuguese: 'Não foi possível identificar o usuário', tok_pisin: 'No inap save user', indonesian: 'Tidak dapat mengidentifikasi pengguna', - nepali: 'प्रयोगकर्ता पहिचान गर्न असमर्थ' + nepali: 'प्रयोगकर्ता पहिचान गर्न असमर्थ', + hindi: 'उपयोगकर्ता की पहचान करने में असमर्थ', + burmese: 'အသုံးပြုသူကို ခွဲခြားမရပါ', + thai: 'ไม่สามารถระบุผู้ใช้ได้', + mandarin: '无法识别用户' }, cannotChangeTranslationSettings: { english: 'Unathorized to change settings for this translation', @@ -2278,7 +3472,12 @@ export const localizations = { tok_pisin: 'Yu no gat rait long senisim settings bilong dispela translation', indonesian: 'Tidak berwenang untuk mengubah pengaturan terjemahan ini', - nepali: 'यो अनुवादको सेटिङहरू परिवर्तन गर्न अनधिकृत' + nepali: 'यो अनुवादको सेटिङहरू परिवर्तन गर्न अनधिकृत', + hindi: 'इस अनुवाद के लिए सेटिंग्स बदलने के लिए अनधिकृत', + burmese: + 'ဤဘာသာပြန်ဆိုချက်အတွက် ဆက်တင်များကို ပြောင်းလဲရန် ခွင့်ပြုချက်မရှိပါ', + thai: 'ไม่มีสิทธิ์ในการเปลี่ยนการตั้งค่าสำหรับคำแปลนี้', + mandarin: '无权更改此翻译的设置' }, alreadyReportedTranslation: { english: 'You have already reported this translation', @@ -2286,7 +3485,11 @@ export const localizations = { brazilian_portuguese: 'Você já reportou esta tradução', tok_pisin: 'Yu reportim dispela translation pinis', indonesian: 'Anda sudah melaporkan terjemahan ini', - nepali: 'तपाईंले पहिले नै यो अनुवाद रिपोर्ट गरिसक्नुभयो' + nepali: 'तपाईंले पहिले नै यो अनुवाद रिपोर्ट गरिसक्नुभयो', + hindi: 'आपने पहले ही इस अनुवाद को रिपोर्ट कर दिया है', + burmese: 'သင်သည် ဤဘာသာပြန်ဆိုချက်ကို သတင်းပို့ပြီးပါပြီ', + thai: 'คุณได้รายงานคำแปลนี้แล้ว', + mandarin: '您已经报告了此翻译' }, failedSaveAnalyticsPreference: { english: 'Failed to save analytics preference', @@ -2294,7 +3497,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao salvar preferência de análise', tok_pisin: 'I no inap seivim analytics preference', indonesian: 'Gagal menyimpan preferensi analitik', - nepali: 'विश्लेषण प्राथमिकता सेभ गर्न असफल' + nepali: 'विश्लेषण प्राथमिकता सेभ गर्न असफल', + hindi: 'विश्लेषण प्राथमिकता सहेजने में विफल', + burmese: 'ခွဲခြမ်းစိတ်ဖြာမှု ဦးစားပေးမှုကို သိမ်းဆည်း၍မရပါ', + thai: 'บันทึกการตั้งค่าการวิเคราะห์ไม่สำเร็จ', + mandarin: '保存分析首选项失败' }, currentPasswordRequired: { english: 'Current password is required', @@ -2302,7 +3509,11 @@ export const localizations = { brazilian_portuguese: 'A senha atual é obrigatória', tok_pisin: 'Password bilong nau i mas', indonesian: 'Kata sandi saat ini diperlukan', - nepali: 'हालको पासवर्ड आवश्यक छ' + nepali: 'हालको पासवर्ड आवश्यक छ', + hindi: 'वर्तमान पासवर्ड आवश्यक है', + burmese: 'လက်ရှိ စကားဝှက် လိုအပ်ပါသည်', + thai: 'ต้องใช้รหัสผ่านปัจจุบัน', + mandarin: '需要当前密码' }, profileUpdateSuccess: { english: 'Profile updated successfully', @@ -2310,7 +3521,11 @@ export const localizations = { brazilian_portuguese: 'Perfil atualizado com sucesso', tok_pisin: 'Profile i update gut', indonesian: 'Profil berhasil diperbarui', - nepali: 'प्रोफाइल सफलतापूर्वक अपडेट गरियो' + nepali: 'प्रोफाइल सफलतापूर्वक अपडेट गरियो', + hindi: 'प्रोफ़ाइल सफलतापूर्वक अपडेट हो गई', + burmese: 'ကိုယ်ရေးအကျဉ်းကို အောင်မြင်စွာ အပ်ဒိတ်လုပ်ပြီးပါပြီ', + thai: 'อัปเดตโปรไฟล์สำเร็จ', + mandarin: '个人资料更新成功' }, failedUpdateProfile: { english: 'Failed to update profile', @@ -2318,7 +3533,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao atualizar perfil', tok_pisin: 'I no inap updateim profile', indonesian: 'Gagal memperbarui profil', - nepali: 'प्रोफाइल अपडेट गर्न असफल' + nepali: 'प्रोफाइल अपडेट गर्न असफल', + hindi: 'प्रोफ़ाइल अपडेट करने में विफल', + burmese: 'ကိုယ်ရေးအကျဉ်းကို အပ်ဒိတ်လုပ်၍မရပါ', + thai: 'อัปเดตโปรไฟล์ไม่สำเร็จ', + mandarin: '更新个人资料失败' }, assetNotFound: { english: 'Asset not found', @@ -2326,7 +3545,11 @@ export const localizations = { brazilian_portuguese: 'Recurso não encontrado', tok_pisin: 'Asset i no stap', indonesian: 'Aset tidak ditemukan', - nepali: 'एसेट फेला परेन' + nepali: 'एसेट फेला परेन', + hindi: 'एसेट नहीं मिला', + burmese: 'ပိုင်ဆိုင်မှု မတွေ့ရှိပါ', + thai: 'ไม่พบสินทรัพย์', + mandarin: '未找到资产' }, failedLoadAssetData: { english: 'Failed to load asset data', @@ -2334,7 +3557,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao carregar dados do recurso', tok_pisin: 'I no inap loadim asset data', indonesian: 'Gagal memuat data aset', - nepali: 'एसेट डाटा लोड गर्न असफल' + nepali: 'एसेट डाटा लोड गर्न असफल', + hindi: 'एसेट डेटा लोड करने में विफल', + burmese: 'ပိုင်ဆိုင်မှု ဒေတာကို လုပ်ဆောင်၍မရပါ', + thai: 'โหลดข้อมูลสินทรัพย์ไม่สำเร็จ', + mandarin: '加载资产数据失败' }, failedLoadAssets: { english: 'Failed to load assets', @@ -2342,7 +3569,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao carregar recursos', tok_pisin: 'I no inap loadim ol asset', indonesian: 'Gagal memuat aset', - nepali: 'एसेटहरू लोड गर्न असफल' + nepali: 'एसेटहरू लोड गर्न असफल', + hindi: 'एसेट लोड करने में विफल', + burmese: 'ပိုင်ဆိုင်မှုများကို လုပ်ဆောင်၍မရပါ', + thai: 'โหลดสินทรัพย์ไม่สำเร็จ', + mandarin: '加载资产失败' }, projectMembers: { english: 'Project Members', @@ -2350,7 +3581,11 @@ export const localizations = { brazilian_portuguese: 'Membros do Projeto', tok_pisin: 'Ol Member bilong Project', indonesian: 'Anggota Proyek', - nepali: 'प्रोजेक्ट सदस्यहरू' + nepali: 'प्रोजेक्ट सदस्यहरू', + hindi: 'परियोजना सदस्य', + burmese: 'စီမံကိန်း အဖွဲ့ဝင်များ', + thai: 'สมาชิกโครงการ', + mandarin: '项目成员' }, members: { english: 'Members', @@ -2358,7 +3593,11 @@ export const localizations = { brazilian_portuguese: 'Membros', tok_pisin: 'Ol Member', indonesian: 'Anggota', - nepali: 'सदस्यहरू' + nepali: 'सदस्यहरू', + hindi: 'सदस्य', + burmese: 'အဖွဲ့ဝင်များ', + thai: 'สมาชิก', + mandarin: '成员' }, invited: { english: 'Invited', @@ -2366,7 +3605,11 @@ export const localizations = { brazilian_portuguese: 'Convidados', tok_pisin: 'Ol i invitim', indonesian: 'Diundang', - nepali: 'आमन्त्रित' + nepali: 'आमन्त्रित', + hindi: 'आमंत्रित', + burmese: 'ဖိတ်ခေါ်ထားသည်', + thai: 'ได้รับเชิญ', + mandarin: '已邀请' }, viewInvitation: { english: 'View Invitation', @@ -2374,7 +3617,11 @@ export const localizations = { brazilian_portuguese: 'Ver Convite', tok_pisin: 'Lukim Invitation', indonesian: 'Lihat Undangan', - nepali: 'आमन्त्रण हेर्नुहोस्' + nepali: 'आमन्त्रण हेर्नुहोस्', + hindi: 'आमंत्रण देखें', + burmese: 'ဖိတ်ခေါ်မှုကို ကြည့်ရှုပါ', + thai: 'ดูคำเชิญ', + mandarin: '查看邀请' }, inviteMembers: { english: 'Invite Members', @@ -2382,7 +3629,11 @@ export const localizations = { brazilian_portuguese: 'Convidar Membros', tok_pisin: 'Invitim ol Member', indonesian: 'Undang Anggota', - nepali: 'सदस्यहरूलाई आमन्त्रित गर्नुहोस्' + nepali: 'सदस्यहरूलाई आमन्त्रित गर्नुहोस्', + hindi: 'सदस्यों को आमंत्रित करें', + burmese: 'အဖွဲ့ဝင်များကို ဖိတ်ခေါ်ပါ', + thai: 'เชิญสมาชิก', + mandarin: '邀请成员' }, inviteAsOwner: { english: 'Invite as owner', @@ -2390,7 +3641,11 @@ export const localizations = { brazilian_portuguese: 'Convidar como proprietário', tok_pisin: 'Invitim olsem owner', indonesian: 'Undang sebagai pemilik', - nepali: 'मालिकको रूपमा आमन्त्रित गर्नुहोस्' + nepali: 'मालिकको रूपमा आमन्त्रित गर्नुहोस्', + hindi: 'मालिक के रूप में आमंत्रित करें', + burmese: 'ပိုင်ရှင်အဖြစ် ဖိတ်ခေါ်ပါ', + thai: 'เชิญเป็นเจ้าของ', + mandarin: '邀请为所有者' }, sendInvitation: { english: 'Send Invitation', @@ -2398,7 +3653,11 @@ export const localizations = { brazilian_portuguese: 'Enviar Convite', tok_pisin: 'Salim Invitation', indonesian: 'Kirim Undangan', - nepali: 'आमन्त्रण पठाउनुहोस्' + nepali: 'आमन्त्रण पठाउनुहोस्', + hindi: 'आमंत्रण भेजें', + burmese: 'ဖိတ်ခေါ်မှုကို ပို့ပါ', + thai: 'ส่งคำเชิญ', + mandarin: '发送邀请' }, owner: { english: 'Owner', @@ -2406,7 +3665,11 @@ export const localizations = { brazilian_portuguese: 'Proprietário', tok_pisin: 'Owner', indonesian: 'Pemilik', - nepali: 'मालिक' + nepali: 'मालिक', + hindi: 'मालिक', + burmese: 'ပိုင်ရှင်', + thai: 'เจ้าของ', + mandarin: '所有者' }, member: { english: 'Member', @@ -2414,7 +3677,11 @@ export const localizations = { brazilian_portuguese: 'Membro', tok_pisin: 'Member', indonesian: 'Anggota', - nepali: 'सदस्य' + nepali: 'सदस्य', + hindi: 'सदस्य', + burmese: 'အဖွဲ့ဝင်', + thai: 'สมาชิก', + mandarin: '成员' }, makeOwner: { english: 'Make Owner', @@ -2422,7 +3689,11 @@ export const localizations = { brazilian_portuguese: 'Tornar Proprietário', tok_pisin: 'Mekim Owner', indonesian: 'Jadikan Pemilik', - nepali: 'मालिक बनाउनुहोस्' + nepali: 'मालिक बनाउनुहोस्', + hindi: 'मालिक बनाएं', + burmese: 'ပိုင်ရှင်အဖြစ် ပြောင်းလဲပါ', + thai: 'ทำให้เป็นเจ้าของ', + mandarin: '设为所有者' }, remove: { english: 'Remove', @@ -2430,7 +3701,11 @@ export const localizations = { brazilian_portuguese: 'Remover', tok_pisin: 'Rausim', indonesian: 'Hapus', - nepali: 'हटाउनुहोस्' + nepali: 'हटाउनुहोस्', + hindi: 'हटाएं', + burmese: 'ဖယ်ရှားပါ', + thai: 'ลบ', + mandarin: '移除' }, withdrawInvite: { english: 'Withdraw Invite', @@ -2438,7 +3713,11 @@ export const localizations = { brazilian_portuguese: 'Retirar Convite', tok_pisin: 'Rausim Invite', indonesian: 'Tarik Undangan', - nepali: 'आमन्त्रण फिर्ता लिनुहोस्' + nepali: 'आमन्त्रण फिर्ता लिनुहोस्', + hindi: 'आमंत्रण वापस लें', + burmese: 'ဖိတ်ခေါ်မှုကို ရုပ်သိမ်းပါ', + thai: 'ถอนคำเชิญ', + mandarin: '撤回邀请' }, you: { english: 'You', @@ -2446,7 +3725,11 @@ export const localizations = { brazilian_portuguese: 'Você', tok_pisin: 'Yu', indonesian: 'Anda', - nepali: 'तपाईं' + nepali: 'तपाईं', + hindi: 'आप', + burmese: 'သင်သည်', + thai: 'คุณ', + mandarin: '您' }, pendingInvitation: { english: 'Pending', @@ -2454,7 +3737,11 @@ export const localizations = { brazilian_portuguese: 'Pendente', tok_pisin: 'Wet', indonesian: 'Tertunda', - nepali: 'बाँकी' + nepali: 'बाँकी', + hindi: 'लंबित', + burmese: 'စောင့်ဆိုင်းနေသည်', + thai: 'รอดำเนินการ', + mandarin: '待处理' }, noMembers: { english: 'No members yet', @@ -2462,7 +3749,11 @@ export const localizations = { brazilian_portuguese: 'Ainda não há membros', tok_pisin: 'No gat member yet', indonesian: 'Belum ada anggota', - nepali: 'अहिलेसम्म कुनै सदस्य छैन' + nepali: 'अहिलेसम्म कुनै सदस्य छैन', + hindi: 'अभी तक कोई सदस्य नहीं', + burmese: 'အခုထိ အဖွဲ့ဝင် မရှိသေးပါ', + thai: 'ยังไม่มีสมาชิก', + mandarin: '还没有成员' }, noInvitations: { english: 'No pending invitations', @@ -2470,7 +3761,11 @@ export const localizations = { brazilian_portuguese: 'Nenhum convite pendente', tok_pisin: 'No gat invitation i wet', indonesian: 'Tidak ada undangan tertunda', - nepali: 'कुनै बाँकी आमन्त्रण छैन' + nepali: 'कुनै बाँकी आमन्त्रण छैन', + hindi: 'कोई लंबित आमंत्रण नहीं', + burmese: 'စောင့်ဆိုင်းနေသော ဖိတ်ခေါ်မှု မရှိပါ', + thai: 'ไม่มีคำเชิญที่รอดำเนินการ', + mandarin: '没有待处理的邀请' }, ownerTooltip: { english: @@ -2484,7 +3779,14 @@ export const localizations = { indonesian: 'Pemilik dapat membuat konten, mengundang dan mempromosikan anggota lain, dan tidak dapat diturunkan kembali ke keanggotaan atau dihapus dari proyek oleh anggota lain.', nepali: - 'मालिकहरूले सामग्री सिर्जना गर्न, अन्य सदस्यहरूलाई आमन्त्रित गर्न र प्रवर्द्धन गर्न सक्छन्, र अन्य सदस्यहरूद्वारा सदस्यतामा फिर्ता गिराउन वा प्रोजेक्टबाट हटाउन सकिँदैन।' + 'मालिकहरूले सामग्री सिर्जना गर्न, अन्य सदस्यहरूलाई आमन्त्रित गर्न र प्रवर्द्धन गर्न सक्छन्, र अन्य सदस्यहरूद्वारा सदस्यतामा फिर्ता गिराउन वा प्रोजेक्टबाट हटाउन सकिँदैन।', + hindi: + 'मालिक सामग्री बना सकते हैं, अन्य सदस्यों को आमंत्रित और प्रोन्नत कर सकते हैं, और अन्य सदस्यों द्वारा सदस्यता में वापस पदावनत या परियोजना से हटाए नहीं जा सकते।', + burmese: + 'ပိုင်ရှင်များသည် အကြောင်းအရာ ဖန်တီးနိုင်ပြီး၊ အခြားအဖွဲ့ဝင်များကို ဖိတ်ခေါ်နိုင်ပြီး မြှင့်တင်နိုင်သော်လည်း၊ အခြားအဖွဲ့ဝင်များက အဖွဲ့ဝင်အဖြစ်သို့ ပြန်လည်နှိမ့်ချခြင်း သို့မဟုတ် စီမံကိန်းမှ ဖယ်ရှားခြင်း မပြုလုပ်နိုင်ပါ။', + thai: 'เจ้าของสามารถสร้างเนื้อหา เชิญและเลื่อนตำแหน่งสมาชิกคนอื่นๆ และไม่สามารถถูกลดตำแหน่งกลับเป็นสมาชิกหรือถูกลบออกจากโครงการโดยสมาชิกคนอื่นๆ ได้', + mandarin: + '所有者可以创建内容、邀请和提升其他成员,并且不能被其他成员降级回成员身份或从项目中移除。' }, confirmRemoveMessage: { english: 'Are you sure you want to remove {name} from this project?', @@ -2494,7 +3796,11 @@ export const localizations = { tok_pisin: 'Yu tru laik rausim {name} long dispela project?', indonesian: 'Apakah Anda yakin ingin menghapus {name} dari proyek ini?', nepali: - 'के तपाईं निश्चित हुनुहुन्छ कि तपाईं {name} लाई यो प्रोजेक्टबाट हटाउन चाहनुहुन्छ?' + 'के तपाईं निश्चित हुनुहुन्छ कि तपाईं {name} लाई यो प्रोजेक्टबाट हटाउन चाहनुहुन्छ?', + hindi: 'क्या आप वाकई {name} को इस परियोजना से हटाना चाहते हैं?', + burmese: 'သင်သည် {name} ကို ဤစီမံကိန်းမှ ဖယ်ရှားလိုပါသလား?', + thai: 'คุณแน่ใจหรือไม่ว่าต้องการลบ {name} ออกจากโครงการนี้?', + mandarin: '您确定要从该项目中移除 {name} 吗?' }, confirmPromote: { english: 'Confirm Promote', @@ -2502,7 +3808,11 @@ export const localizations = { brazilian_portuguese: 'Confirmar Promoção', tok_pisin: 'Confirm Promote', indonesian: 'Konfirmasi Promosi', - nepali: 'प्रमोशन पुष्टि गर्नुहोस्' + nepali: 'प्रमोशन पुष्टि गर्नुहोस्', + hindi: 'प्रोन्नति की पुष्टि करें', + burmese: 'မြှင့်တင်မှုကို အတည်ပြုပါ', + thai: 'ยืนยันการเลื่อนตำแหน่ง', + mandarin: '确认提升' }, confirmPromoteMessage: { english: @@ -2516,7 +3826,13 @@ export const localizations = { indonesian: 'Apakah Anda yakin ingin menjadikan {name} sebagai pemilik? Tindakan ini tidak dapat dibatalkan.', nepali: - 'के तपाईं निश्चित हुनुहुन्छ कि तपाईं {name} लाई मालिक बनाउन चाहनुहुन्छ? यो कार्य पूर्ववत गर्न सकिँदैन।' + 'के तपाईं निश्चित हुनुहुन्छ कि तपाईं {name} लाई मालिक बनाउन चाहनुहुन्छ? यो कार्य पूर्ववत गर्न सकिँदैन।', + hindi: + 'क्या आप वाकई {name} को मालिक बनाना चाहते हैं? यह कार्रवाई पूर्ववत नहीं की जा सकती।', + burmese: + 'သင်သည် {name} ကို ပိုင်ရှင်အဖြစ် ပြောင်းလဲလိုပါသလား? ဤလုပ်ဆောင်ချက်ကို ပြန်လည်ပြုပြင်မရပါ။', + thai: 'คุณแน่ใจหรือไม่ว่าต้องการทำให้ {name} เป็นเจ้าของ? การดำเนินการนี้ไม่สามารถยกเลิกได้', + mandarin: '您确定要让 {name} 成为所有者吗?此操作无法撤消。' }, confirmLeave: { english: 'Leave Project', @@ -2524,7 +3840,11 @@ export const localizations = { brazilian_portuguese: 'Sair do Projeto', tok_pisin: 'Lusim Project', indonesian: 'Tinggalkan Proyek', - nepali: 'प्रोजेक्ट छोड्नुहोस्' + nepali: 'प्रोजेक्ट छोड्नुहोस्', + hindi: 'परियोजना छोड़ें', + burmese: 'စီမံကိန်းမှ ထွက်ပါ', + thai: 'ออกจากโครงการ', + mandarin: '离开项目' }, confirmLeaveMessage: { english: 'Are you sure you want to leave this project?', @@ -2532,7 +3852,11 @@ export const localizations = { brazilian_portuguese: 'Tem certeza de que deseja sair deste projeto?', tok_pisin: 'Yu tru laik lusim dispela project?', indonesian: 'Apakah Anda yakin ingin meninggalkan proyek ini?', - nepali: 'के तपाईं यो प्रोजेक्ट छोड्न निश्चित हुनुहुन्छ?' + nepali: 'के तपाईं यो प्रोजेक्ट छोड्न निश्चित हुनुहुन्छ?', + hindi: 'क्या आप वाकई इस परियोजना को छोड़ना चाहते हैं?', + burmese: 'သင်သည် ဤစီမံကိန်းမှ ထွက်လိုပါသလား?', + thai: 'คุณแน่ใจหรือไม่ว่าต้องการออกจากโครงการนี้?', + mandarin: '您确定要离开此项目吗?' }, cannotLeaveAsOnlyOwner: { english: @@ -2546,7 +3870,14 @@ export const localizations = { indonesian: 'Anda tidak dapat meninggalkan proyek ini karena Anda adalah satu-satunya pemilik. Silakan promosikan anggota lain menjadi pemilik terlebih dahulu.', nepali: - 'तपाईं एक्लो मालिक भएको हुनाले यो प्रोजेक्ट छोड्न सक्नुहुन्न। कृपया पहिले अर्को सदस्यलाई मालिक बनाउनुहोस्।' + 'तपाईं एक्लो मालिक भएको हुनाले यो प्रोजेक्ट छोड्न सक्नुहुन्न। कृपया पहिले अर्को सदस्यलाई मालिक बनाउनुहोस्।', + hindi: + 'आप इस परियोजना को नहीं छोड़ सकते क्योंकि आप एकमात्र मालिक हैं। कृपया पहले किसी अन्य सदस्य को मालिक बनाएं।', + burmese: + 'သင်သည် တစ်ဦးတည်းသော ပိုင်ရှင်ဖြစ်သောကြောင့် ဤစီမံကိန်းမှ ထွက်၍မရပါ။ ကျေးဇူးပြု၍ အခြားအဖွဲ့ဝင်တစ်ဦးကို ပိုင်ရှင်အဖြစ် မြှင့်တင်ပါ။', + thai: 'คุณไม่สามารถออกจากโครงการนี้ได้เนื่องจากคุณเป็นเจ้าของคนเดียว กรุณาเลื่อนตำแหน่งสมาชิกคนอื่นเป็นเจ้าของก่อน', + mandarin: + '您不能离开此项目,因为您是唯一的所有者。请先将另一个成员提升为所有者。' }, invitationAlreadySent: { english: 'An invitation has already been sent to this email address.', @@ -2556,7 +3887,11 @@ export const localizations = { 'Um convite já foi enviado para este endereço de e-mail.', tok_pisin: 'Invitation i go pinis long dispela email adres.', indonesian: 'Undangan sudah dikirim ke alamat email ini.', - nepali: 'यो इमेल ठेगानामा पहिले नै आमन्त्रण पठाइसकिएको छ।' + nepali: 'यो इमेल ठेगानामा पहिले नै आमन्त्रण पठाइसकिएको छ।', + hindi: 'इस ईमेल पते पर पहले से ही एक आमंत्रण भेजा जा चुका है।', + burmese: 'ဤအီးမေးလ်လိပ်စာသို့ ဖိတ်ခေါ်မှုကို ပို့ပြီးပါပြီ။', + thai: 'ได้ส่งคำเชิญไปยังที่อยู่อีเมลนี้แล้ว', + mandarin: '已向此电子邮件地址发送了邀请。' }, invitationSent: { english: 'Invitation sent successfully', @@ -2564,7 +3899,11 @@ export const localizations = { brazilian_portuguese: 'Convite enviado com sucesso', tok_pisin: 'Invitation i go gut', indonesian: 'Undangan berhasil dikirim', - nepali: 'आमन्त्रण सफलतापूर्वक पठाइयो' + nepali: 'आमन्त्रण सफलतापूर्वक पठाइयो', + hindi: 'आमंत्रण सफलतापूर्वक भेजा गया', + burmese: 'ဖိတ်ခေါ်မှုကို အောင်မြင်စွာ ပို့ပြီးပါပြီ', + thai: 'ส่งคำเชิญสำเร็จ', + mandarin: '邀请发送成功' }, expiredInvitation: { english: 'Expired', @@ -2572,7 +3911,11 @@ export const localizations = { brazilian_portuguese: 'Expirado', tok_pisin: 'Pinis', indonesian: 'Kedaluwarsa', - nepali: 'समाप्त भयो' + nepali: 'समाप्त भयो', + hindi: 'समाप्त', + burmese: 'သက်တမ်းကုန်ဆုံးပါပြီ', + thai: 'หมดอายุ', + mandarin: '已过期' }, declinedInvitation: { english: 'Declined', @@ -2580,7 +3923,11 @@ export const localizations = { brazilian_portuguese: 'Recusado', tok_pisin: 'Refusim', indonesian: 'Ditolak', - nepali: 'अस्वीकृत' + nepali: 'अस्वीकृत', + hindi: 'अस्वीकृत', + burmese: 'ငြင်းဆိုပါသည်', + thai: 'ปฏิเสธ', + mandarin: '已拒绝' }, withdrawnInvitation: { english: 'Withdrawn', @@ -2588,7 +3935,11 @@ export const localizations = { brazilian_portuguese: 'Retirado', tok_pisin: 'Rausim', indonesian: 'Ditarik', - nepali: 'फिर्ता लिइएको' + nepali: 'फिर्ता लिइएको', + hindi: 'वापस लिया गया', + burmese: 'ရုပ်သိမ်းပြီး', + thai: 'ถอนแล้ว', + mandarin: '已撤回' }, sending: { english: 'Sending...', @@ -2596,7 +3947,11 @@ export const localizations = { brazilian_portuguese: 'Enviando...', tok_pisin: 'Salim...', indonesian: 'Mengirim...', - nepali: 'पठाउँदै...' + nepali: 'पठाउँदै...', + hindi: 'भेज रहे हैं...', + burmese: 'ပို့နေသည်...', + thai: 'กำลังส่ง...', + mandarin: '正在发送...' }, failedToRemoveMember: { english: 'Failed to remove member', @@ -2604,7 +3959,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao remover membro', tok_pisin: 'I no inap rausim member', indonesian: 'Gagal menghapus anggota', - nepali: 'सदस्य हटाउन असफल' + nepali: 'सदस्य हटाउन असफल', + hindi: 'सदस्य हटाने में विफल', + burmese: 'အဖွဲ့ဝင်ကို ဖယ်ရှား၍မရပါ', + thai: 'ลบสมาชิกไม่สำเร็จ', + mandarin: '移除成员失败' }, failedToPromoteMember: { english: 'Failed to promote member', @@ -2612,7 +3971,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao promover membro', tok_pisin: 'I no inap promotim member', indonesian: 'Gagal mempromosikan anggota', - nepali: 'सदस्यलाई प्रमोट गर्न असफल' + nepali: 'सदस्यलाई प्रमोट गर्न असफल', + hindi: 'सदस्य को प्रोन्नत करने में विफल', + burmese: 'အဖွဲ့ဝင်ကို မြှင့်တင်၍မရပါ', + thai: 'เลื่อนตำแหน่งสมาชิกไม่สำเร็จ', + mandarin: '提升成员失败' }, failedToLeaveProject: { english: 'Failed to leave project', @@ -2620,7 +3983,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao sair do projeto', tok_pisin: 'I no inap lusim project', indonesian: 'Gagal meninggalkan proyek', - nepali: 'प्रोजेक्ट छोड्न असफल' + nepali: 'प्रोजेक्ट छोड्न असफल', + hindi: 'परियोजना छोड़ने में विफल', + burmese: 'စီမံကိန်းမှ ထွက်၍မရပါ', + thai: 'ออกจากโครงการไม่สำเร็จ', + mandarin: '离开项目失败' }, failedToWithdrawInvitation: { english: 'Failed to withdraw invitation', @@ -2628,7 +3995,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao retirar o convite', tok_pisin: 'I no inap rausim invitation', indonesian: 'Gagal menarik undangan', - nepali: 'आमन्त्रण फिर्ता लिन असफल' + nepali: 'आमन्त्रण फिर्ता लिन असफल', + hindi: 'आमंत्रण वापस लेने में विफल', + burmese: 'ဖိတ်ခေါ်မှုကို ရုပ်သိမ်း၍မရပါ', + thai: 'ถอนคำเชิญไม่สำเร็จ', + mandarin: '撤回邀请失败' }, failedToSendInvitation: { english: 'Failed to send invitation', @@ -2636,7 +4007,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao enviar o convite', tok_pisin: 'I no inap salim invitation', indonesian: 'Gagal mengirim undangan', - nepali: 'आमन्त्रण पठाउन असफल' + nepali: 'आमन्त्रण पठाउन असफल', + hindi: 'आमंत्रण भेजने में विफल', + burmese: 'ဖိတ်ခေါ်မှုကို ပို့၍မရပါ', + thai: 'ส่งคำเชิญไม่สำเร็จ', + mandarin: '发送邀请失败' }, privateProject: { english: 'Private Project', @@ -2644,7 +4019,11 @@ export const localizations = { brazilian_portuguese: 'Projeto Privado', tok_pisin: 'Private Project', indonesian: 'Proyek Pribadi', - nepali: 'निजी प्रोजेक्ट' + nepali: 'निजी प्रोजेक्ट', + hindi: 'निजी परियोजना', + burmese: 'ကိုယ်ပိုင် စီမံကိန်း', + thai: 'โครงการส่วนตัว', + mandarin: '私有项目' }, privateProjectDescription: { english: @@ -2658,7 +4037,13 @@ export const localizations = { indonesian: 'Proyek terbuka untuk dilihat oleh siapa saja, tetapi hanya anggota yang dapat berkontribusi.', nepali: - 'प्रोजेक्ट जोसुकैले हेर्नको लागि खुला छ, तर सदस्यहरूले मात्र योगदान गर्न सक्छन्।' + 'प्रोजेक्ट जोसुकैले हेर्नको लागि खुला छ, तर सदस्यहरूले मात्र योगदान गर्न सक्छन्।', + hindi: + 'परियोजना किसी के भी देखने के लिए खुली है, लेकिन केवल सदस्य ही योगदान दे सकते हैं।', + burmese: + 'စီမံကိန်းကို မည်သူမဆို ကြည့်ရှုနိုင်သော်လည်း၊ အဖွဲ့ဝင်များသာ ပံ့ပိုးနိုင်သည်။', + thai: 'โครงการเปิดให้ทุกคนดูได้ แต่เฉพาะสมาชิกเท่านั้นที่สามารถมีส่วนร่วมได้', + mandarin: '该项目对任何人开放查看,但只有成员可以贡献。' }, privateProjectInfo: { english: @@ -2672,7 +4057,14 @@ export const localizations = { indonesian: 'Untuk berkontribusi pada proyek ini, Anda perlu meminta keanggotaan. Pemilik proyek akan meninjau permintaan Anda.', nepali: - 'यो प्रोजेक्टमा योगदान गर्न, तपाईंले सदस्यता अनुरोध गर्नुपर्छ। प्रोजेक्ट मालिकहरूले तपाईंको अनुरोध समीक्षा गर्नेछन्।' + 'यो प्रोजेक्टमा योगदान गर्न, तपाईंले सदस्यता अनुरोध गर्नुपर्छ। प्रोजेक्ट मालिकहरूले तपाईंको अनुरोध समीक्षा गर्नेछन्।', + hindi: + 'इस परियोजना में योगदान देने के लिए, आपको सदस्यता का अनुरोध करना होगा। परियोजना मालिक आपके अनुरोध की समीक्षा करेंगे।', + burmese: + 'ဤစီမံကိန်းသို့ ပံ့ပိုးရန်၊ သင်သည် အဖွဲ့ဝင်အဖြစ် တောင်းဆိုရမည်။ စီမံကိန်း ပိုင်ရှင်များက သင်၏ တောင်းဆိုမှုကို စိစစ်ပါမည်။', + thai: 'เพื่อมีส่วนร่วมในโครงการนี้ คุณต้องขอเป็นสมาชิก เจ้าของโครงการจะตรวจสอบคำขอของคุณ', + mandarin: + '要为此项目做出贡献,您需要请求成员资格。项目所有者将审查您的请求。' }, privateProjectNotLoggedIn: { english: @@ -2685,7 +4077,14 @@ export const localizations = { 'Dispela i private project. Yu mas login pastaim long askim access.', indonesian: 'Ini adalah proyek pribadi. Anda harus masuk untuk meminta akses.', - nepali: 'यो एउटा निजी प्रोजेक्ट हो। पहुँच अनुरोध गर्न तपाईं लग इन हुनुपर्छ।' + nepali: + 'यो एउटा निजी प्रोजेक्ट हो। पहुँच अनुरोध गर्न तपाईं लग इन हुनुपर्छ।', + hindi: + 'यह एक निजी परियोजना है। पहुंच का अनुरोध करने के लिए आपको लॉग इन होना होगा।', + burmese: + '၎င်းသည် ကိုယ်ပိုင် စီမံကိန်းတစ်ခု ဖြစ်သည်။ ဝင်ရောက်ခွင့် တောင်းဆိုရန် သင်သည် အကောင့်ဝင်ရမည်။', + thai: 'นี่คือโครงการส่วนตัว คุณต้องเข้าสู่ระบบเพื่อขอสิทธิ์เข้าถึง', + mandarin: '这是一个私有项目。您必须登录才能请求访问。' }, privateProjectLoginRequired: { english: 'Please sign in to request membership to this private project.', @@ -2696,7 +4095,13 @@ export const localizations = { tok_pisin: 'Plis sign in long askim membership long dispela private project.', indonesian: 'Silakan masuk untuk meminta keanggotaan proyek pribadi ini.', - nepali: 'कृपया यो निजी प्रोजेक्टमा सदस्यता अनुरोध गर्न साइन इन गर्नुहोस्।' + nepali: 'कृपया यो निजी प्रोजेक्टमा सदस्यता अनुरोध गर्न साइन इन गर्नुहोस्।', + hindi: + 'कृपया इस निजी परियोजना में सदस्यता का अनुरोध करने के लिए साइन इन करें।', + burmese: + 'ကျေးဇူးပြု၍ ဤကိုယ်ပိုင် စီမံကိန်းသို့ အဖွဲ့ဝင်အဖြစ် တောင်းဆိုရန် အကောင့်ဝင်ပါ။', + thai: 'กรุณาเข้าสู่ระบบเพื่อขอเป็นสมาชิกในโครงการส่วนตัวนี้', + mandarin: '请登录以请求此私有项目的成员资格。' }, requestMembership: { english: 'Request Membership', @@ -2704,7 +4109,11 @@ export const localizations = { brazilian_portuguese: 'Solicitar Associação', tok_pisin: 'Askim Membership', indonesian: 'Minta Keanggotaan', - nepali: 'सदस्यता अनुरोध गर्नुहोस्' + nepali: 'सदस्यता अनुरोध गर्नुहोस्', + hindi: 'सदस्यता का अनुरोध करें', + burmese: 'အဖွဲ့ဝင်အဖြစ် တောင်းဆိုပါ', + thai: 'ขอเป็นสมาชิก', + mandarin: '请求成员资格' }, requesting: { english: 'Requesting...', @@ -2712,7 +4121,11 @@ export const localizations = { brazilian_portuguese: 'Solicitando...', tok_pisin: 'Askim...', indonesian: 'Meminta...', - nepali: 'अनुरोध गर्दै...' + nepali: 'अनुरोध गर्दै...', + hindi: 'अनुरोध कर रहे हैं...', + burmese: 'တောင်းဆိုနေသည်...', + thai: 'กำลังขอ...', + mandarin: '正在请求...' }, requestPending: { english: 'Request Pending', @@ -2720,7 +4133,11 @@ export const localizations = { brazilian_portuguese: 'Solicitação Pendente', tok_pisin: 'Request i wet', indonesian: 'Permintaan Tertunda', - nepali: 'अनुरोध बाँकी छ' + nepali: 'अनुरोध बाँकी छ', + hindi: 'अनुरोध लंबित', + burmese: 'တောင်းဆိုမှု စောင့်ဆိုင်းနေသည်', + thai: 'คำขอรอดำเนินการ', + mandarin: '请求待处理' }, requestPendingDescription: { english: 'Your membership request is pending review by the project owners.', @@ -2733,7 +4150,13 @@ export const localizations = { indonesian: 'Permintaan keanggotaan Anda sedang menunggu tinjauan oleh pemilik proyek.', nepali: - 'तपाईंको सदस्यता अनुरोध प्रोजेक्ट मालिकहरूद्वारा समीक्षाको लागि पर्खिरहेको छ।' + 'तपाईंको सदस्यता अनुरोध प्रोजेक्ट मालिकहरूद्वारा समीक्षाको लागि पर्खिरहेको छ।', + hindi: + 'आपका सदस्यता अनुरोध परियोजना मालिकों द्वारा समीक्षा के लिए लंबित है।', + burmese: + 'သင်၏ အဖွဲ့ဝင်အဖြစ် တောင်းဆိုမှုကို စီမံကိန်း ပိုင်ရှင်များက စိစစ်နေသည်။', + thai: 'คำขอเป็นสมาชิกของคุณกำลังรอการตรวจสอบโดยเจ้าของโครงการ', + mandarin: '您的成员资格请求正在等待项目所有者审核。' }, withdrawRequest: { english: 'Withdraw Request', @@ -2741,7 +4164,11 @@ export const localizations = { brazilian_portuguese: 'Retirar Solicitação', tok_pisin: 'Rausim Request', indonesian: 'Tarik Permintaan', - nepali: 'अनुरोध फिर्ता लिनुहोस्' + nepali: 'अनुरोध फिर्ता लिनुहोस्', + hindi: 'अनुरोध वापस लें', + burmese: 'တောင်းဆိုမှုကို ရုပ်သိမ်းပါ', + thai: 'ถอนคำขอ', + mandarin: '撤回请求' }, withdrawing: { english: 'Withdrawing...', @@ -2749,7 +4176,11 @@ export const localizations = { brazilian_portuguese: 'Retirando...', tok_pisin: 'Rausim...', indonesian: 'Menarik...', - nepali: 'फिर्ता लिँदै...' + nepali: 'फिर्ता लिँदै...', + hindi: 'वापस ले रहे हैं...', + burmese: 'ရုပ်သိမ်းနေသည်...', + thai: 'กำลังถอน...', + mandarin: '正在撤回...' }, confirmWithdraw: { english: 'Withdraw Request', @@ -2757,7 +4188,11 @@ export const localizations = { brazilian_portuguese: 'Retirar Solicitação', tok_pisin: 'Rausim Request', indonesian: 'Tarik Permintaan', - nepali: 'अनुरोध फिर्ता लिनुहोस्' + nepali: 'अनुरोध फिर्ता लिनुहोस्', + hindi: 'अनुरोध वापस लें', + burmese: 'တောင်းဆိုမှုကို ရုပ်သိမ်းပါ', + thai: 'ถอนคำขอ', + mandarin: '撤回请求' }, confirmWithdrawRequestMessage: { english: 'Are you sure you want to withdraw your membership request?', @@ -2766,7 +4201,11 @@ export const localizations = { 'Tem certeza de que deseja retirar sua solicitação de associação?', tok_pisin: 'Yu tru laik rausim membership request bilong yu?', indonesian: 'Apakah Anda yakin ingin menarik permintaan keanggotaan Anda?', - nepali: 'के तपाईं आफ्नो सदस्यता अनुरोध फिर्ता लिन निश्चित हुनुहुन्छ?' + nepali: 'के तपाईं आफ्नो सदस्यता अनुरोध फिर्ता लिन निश्चित हुनुहुन्छ?', + hindi: 'क्या आप वाकई अपना सदस्यता अनुरोध वापस लेना चाहते हैं?', + burmese: 'သင်သည် သင်၏ အဖွဲ့ဝင်အဖြစ် တောင်းဆိုမှုကို ရုပ်သိမ်းလိုပါသလား?', + thai: 'คุณแน่ใจหรือไม่ว่าต้องการถอนคำขอเป็นสมาชิกของคุณ?', + mandarin: '您确定要撤回您的成员资格请求吗?' }, requestWithdrawn: { english: 'Request withdrawn successfully', @@ -2774,7 +4213,11 @@ export const localizations = { brazilian_portuguese: 'Solicitação retirada com sucesso', tok_pisin: 'Request i rausim gut', indonesian: 'Permintaan berhasil ditarik', - nepali: 'अनुरोध सफलतापूर्वक फिर्ता लिइयो' + nepali: 'अनुरोध सफलतापूर्वक फिर्ता लिइयो', + hindi: 'अनुरोध सफलतापूर्वक वापस लिया गया', + burmese: 'တောင်းဆိုမှုကို အောင်မြင်စွာ ရုပ်သိမ်းပြီးပါပြီ', + thai: 'ถอนคำขอสำเร็จ', + mandarin: '请求已成功撤回' }, requestExpired: { english: 'Request Expired', @@ -2782,7 +4225,11 @@ export const localizations = { brazilian_portuguese: 'Solicitação Expirada', tok_pisin: 'Request i pinis', indonesian: 'Permintaan Kedaluwarsa', - nepali: 'अनुरोध समाप्त भयो' + nepali: 'अनुरोध समाप्त भयो', + hindi: 'अनुरोध समाप्त हो गया', + burmese: 'တောင်းဆိုမှု သက်တမ်းကုန်ဆုံးပါပြီ', + thai: 'คำขอหมดอายุ', + mandarin: '请求已过期' }, requestExpiredDescription: { english: @@ -2796,7 +4243,13 @@ export const localizations = { indonesian: 'Permintaan keanggotaan Anda telah kedaluwarsa. Anda dapat mengirim permintaan baru.', nepali: - 'तपाईंको सदस्यता अनुरोध समाप्त भयो। तपाईं नयाँ अनुरोध पेश गर्न सक्नुहुन्छ।' + 'तपाईंको सदस्यता अनुरोध समाप्त भयो। तपाईं नयाँ अनुरोध पेश गर्न सक्नुहुन्छ।', + hindi: + 'आपका सदस्यता अनुरोध समाप्त हो गया है। आप एक नया अनुरोध जमा कर सकते हैं।', + burmese: + 'သင်၏ အဖွဲ့ဝင်အဖြစ် တောင်းဆိုမှု သက်တမ်းကုန်ဆုံးပါပြီ။ သင်သည် တောင်းဆိုမှု အသစ်တစ်ခု ပေးပို့နိုင်သည်။', + thai: 'คำขอเป็นสมาชิกของคุณหมดอายุแล้ว คุณสามารถส่งคำขอใหม่ได้', + mandarin: '您的成员资格请求已过期。您可以提交新请求。' }, requestAgain: { english: 'Request Again', @@ -2804,7 +4257,11 @@ export const localizations = { brazilian_portuguese: 'Solicitar Novamente', tok_pisin: 'Askim Gen', indonesian: 'Minta Lagi', - nepali: 'फेरि अनुरोध गर्नुहोस्' + nepali: 'फेरि अनुरोध गर्नुहोस्', + hindi: 'फिर से अनुरोध करें', + burmese: 'ထပ်မံ တောင်းဆိုပါ', + thai: 'ขออีกครั้ง', + mandarin: '再次请求' }, requestDeclined: { english: 'Request Declined', @@ -2812,7 +4269,11 @@ export const localizations = { brazilian_portuguese: 'Solicitação Recusada', tok_pisin: 'Request i no', indonesian: 'Permintaan Ditolak', - nepali: 'अनुरोध अस्वीकृत' + nepali: 'अनुरोध अस्वीकृत', + hindi: 'अनुरोध अस्वीकृत', + burmese: 'တောင်းဆိုမှု ငြင်းဆိုပါသည်', + thai: 'คำขอถูกปฏิเสธ', + mandarin: '请求已拒绝' }, requestDeclinedCanRetry: { english: @@ -2826,7 +4287,13 @@ export const localizations = { indonesian: 'Permintaan keanggotaan Anda ditolak. Anda memiliki {attempts} percobaan lagi untuk meminta keanggotaan.', nepali: - 'तपाईंको सदस्यता अनुरोध अस्वीकार गरियो। तपाईंसँग सदस्यता अनुरोध गर्न {attempts} थप प्रयासहरू बाँकी छन्।' + 'तपाईंको सदस्यता अनुरोध अस्वीकार गरियो। तपाईंसँग सदस्यता अनुरोध गर्न {attempts} थप प्रयासहरू बाँकी छन्।', + hindi: + 'आपका सदस्यता अनुरोध अस्वीकार कर दिया गया था। आपके पास सदस्यता का अनुरोध करने के लिए {attempts} और प्रयास बचे हैं।', + burmese: + 'သင်၏ အဖွဲ့ဝင်အဖြစ် တောင်းဆိုမှုကို ငြင်းဆိုပါသည်။ အဖွဲ့ဝင်အဖြစ် တောင်းဆိုရန် {attempts} ထပ်မံကြိုးစားခွင့် ရှိပါသေးသည်။', + thai: 'คำขอเป็นสมาชิกของคุณถูกปฏิเสธ คุณมีโอกาสขอเป็นสมาชิกอีก {attempts} ครั้ง', + mandarin: '您的成员资格请求已被拒绝。您还有 {attempts} 次机会请求成员资格。' }, requestDeclinedNoRetry: { english: @@ -2840,7 +4307,13 @@ export const localizations = { indonesian: 'Permintaan keanggotaan Anda ditolak dan Anda telah mencapai jumlah maksimum percobaan.', nepali: - 'तपाईंको सदस्यता अनुरोध अस्वीकार गरियो र तपाईंले अधिकतम प्रयासहरूको सीमा पुगिसक्नुभयो।' + 'तपाईंको सदस्यता अनुरोध अस्वीकार गरियो र तपाईंले अधिकतम प्रयासहरूको सीमा पुगिसक्नुभयो।', + hindi: + 'आपका सदस्यता अनुरोध अस्वीकार कर दिया गया था और आपने अधिकतम प्रयासों की सीमा पहुंच गई है।', + burmese: + 'သင်၏ အဖွဲ့ဝင်အဖြစ် တောင်းဆိုမှုကို ငြင်းဆိုပါသည် နှင့် သင်သည် အများဆုံး ကြိုးစားခွင့် အရေအတွက်သို့ ရောက်ရှိပါပြီ။', + thai: 'คำขอเป็นสมาชิกของคุณถูกปฏิเสธ และคุณถึงจำนวนครั้งสูงสุดแล้ว', + mandarin: '您的成员资格请求已被拒绝,您已达到最大尝试次数。' }, requestWithdrawnTitle: { english: 'Request Withdrawn', @@ -2848,7 +4321,11 @@ export const localizations = { brazilian_portuguese: 'Solicitação Retirada', nepali: 'अनुरोध फिर्ता लिइयो', tok_pisin: 'Request i Rausim', - indonesian: 'Permintaan Ditarik' + indonesian: 'Permintaan Ditarik', + hindi: 'अनुरोध वापस लिया गया', + burmese: 'တောင်းဆိုမှု ရုပ်သိမ်းပြီးပါပြီ', + thai: 'ถอนคำขอแล้ว', + mandarin: '请求已撤回' }, requestWithdrawnDescription: { english: @@ -2862,7 +4339,13 @@ export const localizations = { indonesian: 'Anda telah menarik permintaan keanggotaan Anda. Anda dapat mengirim permintaan baru kapan saja.', nepali: - 'तपाईंले आफ्नो सदस्यता अनुरोध फिर्ता लिनुभयो। तपाईं जुनसुकै समय नयाँ अनुरोध पेश गर्न सक्नुहुन्छ।' + 'तपाईंले आफ्नो सदस्यता अनुरोध फिर्ता लिनुभयो। तपाईं जुनसुकै समय नयाँ अनुरोध पेश गर्न सक्नुहुन्छ।', + hindi: + 'आपने अपना सदस्यता अनुरोध वापस ले लिया है। आप किसी भी समय एक नया अनुरोध जमा कर सकते हैं।', + burmese: + 'သင်သည် သင်၏ အဖွဲ့ဝင်အဖြစ် တောင်းဆိုမှုကို ရုပ်သိမ်းပါပြီ။ သင်သည် မည်သည့်အချိန်တွင်မဆို တောင်းဆိုမှု အသစ်တစ်ခု ပေးပို့နိုင်သည်။', + thai: 'คุณได้ถอนคำขอเป็นสมาชิกแล้ว คุณสามารถส่งคำขอใหม่ได้ตลอดเวลา', + mandarin: '您已撤回您的成员资格请求。您可以随时提交新请求。' }, membershipRequestSent: { english: 'Membership request sent successfully', @@ -2870,7 +4353,11 @@ export const localizations = { brazilian_portuguese: 'Solicitação de associação enviada com sucesso', tok_pisin: 'Membership request i go gut', indonesian: 'Permintaan keanggotaan berhasil dikirim', - nepali: 'सदस्यता अनुरोध सफलतापूर्वक पठाइयो' + nepali: 'सदस्यता अनुरोध सफलतापूर्वक पठाइयो', + hindi: 'सदस्यता अनुरोध सफलतापूर्वक भेजा गया', + burmese: 'အဖွဲ့ဝင်အဖြစ် တောင်းဆိုမှု အောင်မြင်စွာ ပေးပို့ပါပြီ', + thai: 'ส่งคำขอเป็นสมาชิกสำเร็จ', + mandarin: '成员资格请求已成功发送' }, failedToRequestMembership: { english: 'Failed to request membership', @@ -2878,7 +4365,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao solicitar associação', tok_pisin: 'I no inap askim membership', indonesian: 'Gagal meminta keanggotaan', - nepali: 'सदस्यता अनुरोध गर्न असफल' + nepali: 'सदस्यता अनुरोध गर्न असफल', + hindi: 'सदस्यता का अनुरोध करने में विफल', + burmese: 'အဖွဲ့ဝင်အဖြစ် တောင်းဆိုရန် မအောင်မြင်ပါ', + thai: 'ขอเป็นสมาชิกไม่สำเร็จ', + mandarin: '请求成员资格失败' }, failedToWithdrawRequest: { english: 'Failed to withdraw request', @@ -2886,7 +4377,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao retirar a solicitação', tok_pisin: 'I no inap rausim request', indonesian: 'Gagal menarik permintaan', - nepali: 'अनुरोध फिर्ता लिन असफल' + nepali: 'अनुरोध फिर्ता लिन असफल', + hindi: 'अनुरोध वापस लेने में विफल', + burmese: 'တောင်းဆိုမှုကို ရုပ်သိမ်းရန် မအောင်မြင်ပါ', + thai: 'ถอนคำขอไม่สำเร็จ', + mandarin: '撤回请求失败' }, goBack: { english: 'Go Back', @@ -2894,7 +4389,11 @@ export const localizations = { brazilian_portuguese: 'Voltar', tok_pisin: 'Go Bek', indonesian: 'Kembali', - nepali: 'पछाडि जानुहोस्' + nepali: 'पछाडि जानुहोस्', + hindi: 'वापस जाएं', + burmese: 'ပြန်သွားပါ', + thai: 'กลับ', + mandarin: '返回' }, back: { english: 'Back', @@ -2902,7 +4401,11 @@ export const localizations = { brazilian_portuguese: 'Voltar', tok_pisin: 'Go Bek', indonesian: 'Kembali', - nepali: 'पछाडि' + nepali: 'पछाडि', + hindi: 'वापस', + burmese: 'ပြန်', + thai: 'กลับ', + mandarin: '返回' }, confirmRemove: { english: 'Confirm Remove', @@ -2910,7 +4413,11 @@ export const localizations = { brazilian_portuguese: 'Confirmar Remoção', tok_pisin: 'Confirm Rausim', indonesian: 'Konfirmasi Hapus', - nepali: 'हटाउने पुष्टि गर्नुहोस्' + nepali: 'हटाउने पुष्टि गर्नुहोस्', + hindi: 'हटाने की पुष्टि करें', + burmese: 'ဖယ်ရှားရန် အတည်ပြုပါ', + thai: 'ยืนยันการลบ', + mandarin: '确认删除' }, invitationResent: { english: 'Invitation resent successfully', @@ -2918,7 +4425,11 @@ export const localizations = { brazilian_portuguese: 'Convite reenviado com sucesso', tok_pisin: 'Invitation i salim gen gut', indonesian: 'Undangan berhasil dikirim ulang', - nepali: 'आमन्त्रण सफलतापूर्वक पुन: पठाइयो' + nepali: 'आमन्त्रण सफलतापूर्वक पुन: पठाइयो', + hindi: 'आमंत्रण सफलतापूर्वक पुनः भेजा गया', + burmese: 'ဖိတ်ခေါ်စာ အောင်မြင်စွာ ပြန်လည် ပေးပို့ပါပြီ', + thai: 'ส่งคำเชิญอีกครั้งสำเร็จ', + mandarin: '邀请已成功重新发送' }, maxInviteAttemptsReached: { english: 'Maximum invitation attempts reached for this email', @@ -2928,7 +4439,11 @@ export const localizations = { 'Número máximo de tentativas de convite atingido para este e-mail', tok_pisin: 'Maximum invitation chance i pinis long dispela email', indonesian: 'Percobaan undangan maksimum tercapai untuk email ini', - nepali: 'यो इमेलको लागि अधिकतम आमन्त्रण प्रयासहरूको सीमा पुग्यो' + nepali: 'यो इमेलको लागि अधिकतम आमन्त्रण प्रयासहरूको सीमा पुग्यो', + hindi: 'इस ईमेल के लिए अधिकतम आमंत्रण प्रयासों की सीमा पहुंच गई', + burmese: 'ဤအီးမေးလ်အတွက် အများဆုံး ဖိတ်ခေါ်စာ ကြိုးစားခွင့် ရောက်ရှိပါပြီ', + thai: 'ถึงจำนวนครั้งสูงสุดของการส่งคำเชิญสำหรับอีเมลนี้แล้ว', + mandarin: '此电子邮件的最大邀请尝试次数已达上限' }, invitationAcceptedButDownloadFailed: { english: @@ -2942,7 +4457,13 @@ export const localizations = { indonesian: 'Undangan diterima, tetapi unduhan proyek gagal. Anda dapat mengunduhnya nanti dari halaman proyek.', nepali: - 'आमन्त्रण स्वीकार गरियो, तर प्रोजेक्ट डाउनलोड असफल भयो। तपाईं यसलाई पछि प्रोजेक्टहरू पृष्ठबाट डाउनलोड गर्न सक्नुहुन्छ।' + 'आमन्त्रण स्वीकार गरियो, तर प्रोजेक्ट डाउनलोड असफल भयो। तपाईं यसलाई पछि प्रोजेक्टहरू पृष्ठबाट डाउनलोड गर्न सक्नुहुन्छ।', + hindi: + 'आमंत्रण स्वीकार कर लिया गया, लेकिन परियोजना डाउनलोड विफल रहा। आप इसे बाद में परियोजनाएं पृष्ठ से डाउनलोड कर सकते हैं।', + burmese: + 'ဖိတ်ခေါ်စာ လက်ခံပါပြီ၊ သို့သော် စီမံကိန်း ဒေါင်းလုဒ်လုပ်ရန် မအောင်မြင်ပါ။ သင်သည် ၎င်းကို နောက်ပိုင်းတွင် စီမံကိန်းများ စာမျက်နှာမှ ဒေါင်းလုဒ်လုပ်နိုင်သည်။', + thai: 'ยอมรับคำเชิญแล้ว แต่การดาวน์โหลดโครงการล้มเหลว คุณสามารถดาวน์โหลดได้ภายหลังจากหน้าคำเชิญ', + mandarin: '邀请已接受,但项目下载失败。您可以稍后从项目页面下载。' }, invitationAcceptedSuccess: { english: 'Invitation accepted successfully!', @@ -2950,7 +4471,11 @@ export const localizations = { brazilian_portuguese: 'Convite aceito com sucesso!', tok_pisin: 'Invitation i akseptim gut!', indonesian: 'Undangan berhasil diterima!', - nepali: 'आमन्त्रण सफलतापूर्वक स्वीकार गरियो!' + nepali: 'आमन्त्रण सफलतापूर्वक स्वीकार गरियो!', + hindi: 'आमंत्रण सफलतापूर्वक स्वीकार कर लिया गया!', + burmese: 'ဖိတ်ခေါ်စာ အောင်မြင်စွာ လက်ခံပါပြီ!', + thai: 'ยอมรับคำเชิญสำเร็จ!', + mandarin: '邀请已成功接受!' }, invitationDeclined: { english: 'Invitation declined.', @@ -2958,7 +4483,11 @@ export const localizations = { brazilian_portuguese: 'Convite recusado.', tok_pisin: 'Invitation i no.', indonesian: 'Undangan ditolak.', - nepali: 'आमन्त्रण अस्वीकृत।' + nepali: 'आमन्त्रण अस्वीकृत।', + hindi: 'आमंत्रण अस्वीकार कर दिया गया।', + burmese: 'ဖိတ်ခေါ်စာ ငြင်းဆိုပါသည်။', + thai: 'ปฏิเสธคำเชิญ', + mandarin: '邀请已拒绝。' }, joinRequest: { english: 'Join Request', @@ -2966,7 +4495,11 @@ export const localizations = { brazilian_portuguese: 'Solicitação de Adesão', tok_pisin: 'Join Request', indonesian: 'Permintaan Bergabung', - nepali: 'सामेल हुने अनुरोध' + nepali: 'सामेल हुने अनुरोध', + hindi: 'शामिल होने का अनुरोध', + burmese: 'ပါဝင်ရန် တောင်းဆိုမှု', + thai: 'คำขอเข้าร่วม', + mandarin: '加入请求' }, privateProjectAccess: { english: 'Private Project Access', @@ -2974,7 +4507,11 @@ export const localizations = { brazilian_portuguese: 'Acesso ao Projeto Privado', tok_pisin: 'Private Project Access', indonesian: 'Akses Proyek Pribadi', - nepali: 'निजी प्रोजेक्ट पहुँच' + nepali: 'निजी प्रोजेक्ट पहुँच', + hindi: 'निजी परियोजना पहुंच', + burmese: 'ကိုယ်ပိုင် စီမံကိန်း ဝင်ရောက်ခွင့်', + thai: 'การเข้าถึงโครงการส่วนตัว', + mandarin: '私有项目访问' }, privateProjectDownload: { english: 'Private Project Download', @@ -2982,7 +4519,11 @@ export const localizations = { brazilian_portuguese: 'Download de Projeto Privado', tok_pisin: 'Private Project Download', indonesian: 'Unduh Proyek Pribadi', - nepali: 'निजी प्रोजेक्ट डाउनलोड' + nepali: 'निजी प्रोजेक्ट डाउनलोड', + hindi: 'निजी परियोजना डाउनलोड', + burmese: 'ကိုယ်ပိုင် စီမံကိန်း ဒေါင်းလုဒ်', + thai: 'การดาวน์โหลดโครงการส่วนตัว', + mandarin: '私有项目下载' }, privateProjectDownloadMessage: { english: @@ -2996,7 +4537,14 @@ export const localizations = { indonesian: 'Proyek ini pribadi. Anda dapat mengunduh konten tetapi tidak akan dapat berkontribusi terjemahan atau suara. Minta akses untuk bergabung dengan proyek ini dan mulai berkontribusi.', nepali: - 'यो प्रोजेक्ट निजी छ। तपाईं सामग्री डाउनलोड गर्न सक्नुहुन्छ तर अनुवाद वा मतहरू योगदान गर्न सक्षम हुनुहुने छैन। यो प्रोजेक्टमा सामेल हुन र योगदान गर्न सुरु गर्न पहुँच अनुरोध गर्नुहोस्।' + 'यो प्रोजेक्ट निजी छ। तपाईं सामग्री डाउनलोड गर्न सक्नुहुन्छ तर अनुवाद वा मतहरू योगदान गर्न सक्षम हुनुहुने छैन। यो प्रोजेक्टमा सामेल हुन र योगदान गर्न सुरु गर्न पहुँच अनुरोध गर्नुहोस्।', + hindi: + 'यह परियोजना निजी है। आप सामग्री डाउनलोड कर सकते हैं लेकिन अनुवाद या वोट योगदान करने में सक्षम नहीं होंगे। इस परियोजना में शामिल होने और योगदान शुरू करने के लिए पहुंच का अनुरोध करें।', + burmese: + 'ဤစီမံကိန်းသည် ကိုယ်ပိုင်ဖြစ်သည်။ သင်သည် အကြောင်းအရာကို ဒေါင်းလုဒ်လုပ်နိုင်သော်လည်း ဘာသာပြန်ဆိုမှုများ သို့မဟုတ် မဲများကို ပံ့ပိုးပေးနိုင်မည် မဟုတ်ပါ။ ဤစီမံကိန်းတွင် ပါဝင်ရန် နှင့် ပံ့ပိုးပေးရန် စတင်ရန် ဝင်ရောက်ခွင့် တောင်းဆိုပါ။', + thai: 'โครงการนี้เป็นโครงการส่วนตัว คุณสามารถดาวน์โหลดเนื้อหาได้ แต่จะไม่สามารถมีส่วนร่วมในการแปลหรือโหวตได้ ขอการเข้าถึงเพื่อเข้าร่วมโครงการนี้และเริ่มมีส่วนร่วม', + mandarin: + '此项目是私有的。您可以下载内容,但无法贡献翻译或投票。请求访问以加入此项目并开始贡献。' }, privateProjectEditing: { english: 'Private Project Editing', @@ -3004,7 +4552,11 @@ export const localizations = { brazilian_portuguese: 'Edição de Projeto Privado', tok_pisin: 'Private Project Editing', indonesian: 'Pengeditan Proyek Pribadi', - nepali: 'निजी प्रोजेक्ट सम्पादन' + nepali: 'निजी प्रोजेक्ट सम्पादन', + hindi: 'निजी परियोजना संपादन', + burmese: 'ကိုယ်ပိုင်စီမံကိန်း တည်းဖြတ်ခြင်း', + thai: 'การแก้ไขโครงการส่วนตัว', + mandarin: '私有项目编辑' }, privateProjectEditingMessage: { english: @@ -3018,7 +4570,14 @@ export const localizations = { indonesian: 'Proyek ini pribadi. Anda perlu menjadi anggota untuk mengedit transkripsi. Minta akses untuk bergabung dengan proyek ini.', nepali: - 'यो प्रोजेक्ट निजी छ। ट्रान्सक्रिप्सन सम्पादन गर्न तपाईं सदस्य हुनुपर्छ। यो प्रोजेक्टमा सामेल हुन पहुँच अनुरोध गर्नुहोस्।' + 'यो प्रोजेक्ट निजी छ। ट्रान्सक्रिप्सन सम्पादन गर्न तपाईं सदस्य हुनुपर्छ। यो प्रोजेक्टमा सामेल हुन पहुँच अनुरोध गर्नुहोस्।', + hindi: + 'यह परियोजना निजी है। ट्रांसक्रिप्शन संपादित करने के लिए आपको सदस्य होना होगा। इस परियोजना में शामिल होने के लिए पहुंच का अनुरोध करें।', + burmese: + 'ဤစီမံကိန်းသည် ကိုယ်ပိုင်ဖြစ်သည်။ စာသားများကို တည်းဖြတ်ရန် သင်သည် အဖွဲ့ဝင် ဖြစ်ရမည်။ ဤစီမံကိန်းတွင် ပါဝင်ရန် ဝင်ရောက်ခွင့် တောင်းဆိုပါ။', + thai: 'โครงการนี้เป็นโครงการส่วนตัว คุณต้องเป็นสมาชิกเพื่อแก้ไขการถอดความ ขอการเข้าถึงเพื่อเข้าร่วมโครงการนี้', + mandarin: + '此项目是私有的。您需要成为成员才能编辑转录。请求访问以加入此项目。' }, privateProjectGenericMessage: { english: @@ -3032,7 +4591,14 @@ export const localizations = { indonesian: 'Proyek ini pribadi. Anda perlu menjadi anggota untuk mengakses fitur ini. Minta akses untuk bergabung dengan proyek ini.', nepali: - 'यो प्रोजेक्ट निजी छ। यो सुविधा पहुँच गर्न तपाईं सदस्य हुनुपर्छ। यो प्रोजेक्टमा सामेल हुन पहुँच अनुरोध गर्नुहोस्।' + 'यो प्रोजेक्ट निजी छ। यो सुविधा पहुँच गर्न तपाईं सदस्य हुनुपर्छ। यो प्रोजेक्टमा सामेल हुन पहुँच अनुरोध गर्नुहोस्।', + hindi: + 'यह परियोजना निजी है। इस सुविधा तक पहुंचने के लिए आपको सदस्य होना होगा। इस परियोजना में शामिल होने के लिए पहुंच का अनुरोध करें।', + burmese: + 'ဤစီမံကိန်းသည် ကိုယ်ပိုင်ဖြစ်သည်။ ဤအင်္ဂါရပ်ကို ဝင်ရောက်ရန် သင်သည် အဖွဲ့ဝင် ဖြစ်ရမည်။ ဤစီမံကိန်းတွင် ပါဝင်ရန် ဝင်ရောက်ခွင့် တောင်းဆိုပါ။', + thai: 'โครงการนี้เป็นโครงการส่วนตัว คุณต้องเป็นสมาชิกเพื่อเข้าถึงฟีเจอร์นี้ ขอการเข้าถึงเพื่อเข้าร่วมโครงการนี้', + mandarin: + '此项目是私有的。您需要成为成员才能访问此功能。请求访问以加入此项目。' }, privateProjectMembers: { english: 'Private Project Members', @@ -3040,7 +4606,11 @@ export const localizations = { brazilian_portuguese: 'Membros do Projeto Privado', tok_pisin: 'Private Project Members', indonesian: 'Anggota Proyek Pribadi', - nepali: 'निजी प्रोजेक्ट सदस्यहरू' + nepali: 'निजी प्रोजेक्ट सदस्यहरू', + hindi: 'निजी परियोजना सदस्य', + burmese: 'ကိုယ်ပိုင် စီမံကိန်း အဖွဲ့ဝင်များ', + thai: 'สมาชิกโครงการส่วนตัว', + mandarin: '私有项目成员' }, privateProjectMembersMessage: { english: @@ -3054,7 +4624,13 @@ export const localizations = { indonesian: 'Anda perlu menjadi anggota untuk melihat daftar anggota dan mengirim undangan. Minta akses untuk bergabung dengan proyek ini.', nepali: - 'सदस्य सूची हेर्न र आमन्त्रणहरू पठाउन तपाईं सदस्य हुनुपर्छ। यो प्रोजेक्टमा सामेल हुन पहुँच अनुरोध गर्नुहोस्।' + 'सदस्य सूची हेर्न र आमन्त्रणहरू पठाउन तपाईं सदस्य हुनुपर्छ। यो प्रोजेक्टमा सामेल हुन पहुँच अनुरोध गर्नुहोस्।', + hindi: + 'सदस्य सूची देखने और आमंत्रण भेजने के लिए आपको सदस्य होना होगा। इस परियोजना में शामिल होने के लिए पहुंच का अनुरोध करें।', + burmese: + 'အဖွဲ့ဝင်စာရင်းကို ကြည့်ရှုရန် နှင့် ဖိတ်ခေါ်စာများ ပေးပို့ရန် သင်သည် အဖွဲ့ဝင် ဖြစ်ရမည်။ ဤစီမံကိန်းတွင် ပါဝင်ရန် ဝင်ရောက်ခွင့် တောင်းဆိုပါ။', + thai: 'คุณต้องเป็นสมาชิกเพื่อดูรายชื่อสมาชิกและส่งคำเชิญ ขอการเข้าถึงเพื่อเข้าร่วมโครงการนี้', + mandarin: '您需要成为成员才能查看成员列表并发送邀请。请求访问以加入此项目。' }, privateProjectNotLoggedInInline: { english: 'You need to be logged in to access this private project.', @@ -3063,7 +4639,11 @@ export const localizations = { 'Você precisa estar logado para acessar este projeto privado.', tok_pisin: 'Yu mas login pastaim long access dispela private project.', indonesian: 'Anda perlu masuk untuk mengakses proyek pribadi ini.', - nepali: 'यो निजी प्रोजेक्ट पहुँच गर्न तपाईं लग इन हुनुपर्छ।' + nepali: 'यो निजी प्रोजेक्ट पहुँच गर्न तपाईं लग इन हुनुपर्छ।', + hindi: 'इस निजी परियोजना तक पहुंचने के लिए आपको लॉग इन होना होगा।', + burmese: 'ဤကိုယ်ပိုင် စီမံကိန်းကို ဝင်ရောက်ရန် သင်သည် အကောင့်ဝင်ရမည်။', + thai: 'คุณต้องเข้าสู่ระบบเพื่อเข้าถึงโครงการส่วนตัวนี้', + mandarin: '您需要登录才能访问此私有项目。' }, privateProjectTranslation: { english: 'Private Project Translation', @@ -3071,7 +4651,11 @@ export const localizations = { brazilian_portuguese: 'Tradução de Projeto Privado', tok_pisin: 'Private Project Translation', indonesian: 'Terjemahan Proyek Pribadi', - nepali: 'निजी प्रोजेक्ट अनुवाद' + nepali: 'निजी प्रोजेक्ट अनुवाद', + hindi: 'निजी परियोजना अनुवाद', + burmese: 'ကိုယ်ပိုင် စီမံကိန်း ဘာသာပြန်ဆိုခြင်း', + thai: 'การแปลโครงการส่วนตัว', + mandarin: '私有项目翻译' }, privateProjectTranslationMessage: { english: @@ -3085,7 +4669,14 @@ export const localizations = { indonesian: 'Proyek ini pribadi. Anda perlu menjadi anggota untuk mengirim terjemahan. Minta akses untuk bergabung dengan proyek ini.', nepali: - 'यो प्रोजेक्ट निजी छ। अनुवादहरू पेश गर्न तपाईं सदस्य हुनुपर्छ। यो प्रोजेक्टमा सामेल हुन पहुँच अनुरोध गर्नुहोस्।' + 'यो प्रोजेक्ट निजी छ। अनुवादहरू पेश गर्न तपाईं सदस्य हुनुपर्छ। यो प्रोजेक्टमा सामेल हुन पहुँच अनुरोध गर्नुहोस्।', + hindi: + 'यह परियोजना निजी है। अनुवाद जमा करने के लिए आपको सदस्य होना होगा। इस परियोजना में शामिल होने के लिए पहुंच का अनुरोध करें।', + burmese: + 'ဤစီမံကိန်းသည် ကိုယ်ပိုင်ဖြစ်သည်။ ဘာသာပြန်ဆိုမှုများကို ပေးပို့ရန် သင်သည် အဖွဲ့ဝင် ဖြစ်ရမည်။ ဤစီမံကိန်းတွင် ပါဝင်ရန် ဝင်ရောက်ခွင့် တောင်းဆိုပါ။', + thai: 'โครงการนี้เป็นโครงการส่วนตัว คุณต้องเป็นสมาชิกเพื่อส่งการแปล ขอการเข้าถึงเพื่อเข้าร่วมโครงการนี้', + mandarin: + '此项目是私有的。您需要成为成员才能提交翻译。请求访问以加入此项目。' }, privateProjectVoting: { english: 'Private Project Voting', @@ -3093,7 +4684,11 @@ export const localizations = { brazilian_portuguese: 'Votação de Projeto Privado', tok_pisin: 'Private Project Voting', indonesian: 'Pemungutan Suara Proyek Pribadi', - nepali: 'निजी प्रोजेक्ट मतदान' + nepali: 'निजी प्रोजेक्ट मतदान', + hindi: 'निजी परियोजना मतदान', + burmese: 'ကိုယ်ပိုင် စီမံကိန်း မဲပေးခြင်း', + thai: 'การโหวตโครงการส่วนตัว', + mandarin: '私有项目投票' }, privateProjectVotingMessage: { english: @@ -3107,7 +4702,14 @@ export const localizations = { indonesian: 'Proyek ini pribadi. Anda perlu menjadi anggota untuk memilih terjemahan. Minta akses untuk bergabung dengan proyek ini.', nepali: - 'यो प्रोजेक्ट निजी छ। अनुवादहरूमा मतदान गर्न तपाईं सदस्य हुनुपर्छ। यो प्रोजेक्टमा सामेल हुन पहुँच अनुरोध गर्नुहोस्।' + 'यो प्रोजेक्ट निजी छ। अनुवादहरूमा मतदान गर्न तपाईं सदस्य हुनुपर्छ। यो प्रोजेक्टमा सामेल हुन पहुँच अनुरोध गर्नुहोस्।', + hindi: + 'यह परियोजना निजी है। अनुवादों पर मतदान करने के लिए आपको सदस्य होना होगा। इस परियोजना में शामिल होने के लिए पहुंच का अनुरोध करें।', + burmese: + 'ဤစီမံကိန်းသည် ကိုယ်ပိုင်ဖြစ်သည်။ ဘာသာပြန်ဆိုမှုများတွင် မဲပေးရန် သင်သည် အဖွဲ့ဝင် ဖြစ်ရမည်။ ဤစီမံကိန်းတွင် ပါဝင်ရန် ဝင်ရောက်ခွင့် တောင်းဆိုပါ။', + thai: 'โครงการนี้เป็นโครงการส่วนตัว คุณต้องเป็นสมาชิกเพื่อโหวตการแปล ขอการเข้าถึงเพื่อเข้าร่วมโครงการนี้', + mandarin: + '此项目是私有的。您需要成为成员才能对翻译进行投票。请求访问以加入此项目。' }, projectInvitation: { english: 'Project Invitation', @@ -3115,7 +4717,11 @@ export const localizations = { brazilian_portuguese: 'Convite para o Projeto', tok_pisin: 'Project Invitation', indonesian: 'Undangan Proyek', - nepali: 'प्रोजेक्ट आमन्त्रण' + nepali: 'प्रोजेक्ट आमन्त्रण', + hindi: 'परियोजना आमंत्रण', + burmese: 'စီမံကိန်း ဖိတ်ခေါ်စာ', + thai: 'คำเชิญโครงการ', + mandarin: '项目邀请' }, projectInvitationFrom: { english: '{sender} has invited you to join project "{project}" as {role}', @@ -3128,7 +4734,13 @@ export const localizations = { indonesian: '{sender} mengundang Anda untuk bergabung dengan proyek "{project}" sebagai {role}', nepali: - '{sender} ले तपाईंलाई "{project}" प्रोजेक्टमा {role} को रूपमा सामेल हुन आमन्त्रण गर्नुभयो' + '{sender} ले तपाईंलाई "{project}" प्रोजेक्टमा {role} को रूपमा सामेल हुन आमन्त्रण गर्नुभयो', + hindi: + '{sender} ने आपको "{project}" परियोजना में {role} के रूप में शामिल होने के लिए आमंत्रित किया है', + burmese: + '{sender} သည် သင့်အား "{project}" စီမံကိန်းတွင် {role} အဖြစ် ပါဝင်ရန် ဖိတ်ခေါ်ပါသည်', + thai: '{sender} ได้เชิญคุณเข้าร่วมโครงการ "{project}" ในฐานะ {role}', + mandarin: '{sender} 已邀请您以 {role} 身份加入项目 "{project}"' }, projectJoinRequestFrom: { english: '{sender} has requested to join project "{project}" as {role}', @@ -3136,8 +4748,17 @@ export const localizations = { '{sender} ha solicitado unirse al proyecto "{project}" como {role}', brazilian_portuguese: '{sender} solicitou participar do projeto "{project}" como {role}', + tok_pisin: '{sender} i askim long joinim projek "{project}" olsem {role}', + indonesian: + '{sender} telah meminta untuk bergabung dengan proyek "{project}" sebagai {role}', nepali: - '{sender} ले "{project}" प्रोजेक्टमा {role} को रूपमा सामेल हुन अनुरोध गर्नुभयो' + '{sender} ले "{project}" प्रोजेक्टमा {role} को रूपमा सामेल हुन अनुरोध गर्नुभयो', + hindi: + '{sender} ने "{project}" परियोजना में {role} के रूप में शामिल होने का अनुरोध किया है', + burmese: + '{sender} သည် "{project}" စီမံကိန်းတွင် {role} အဖြစ် ပါဝင်ရန် တောင်းဆိုပါသည်', + thai: '{sender} ได้ขอเข้าร่วมโครงการ "{project}" ในฐานะ {role}', + mandarin: '{sender} 已请求以 {role} 身份加入项目 "{project}"' }, projectWillRemainDownloaded: { english: 'Project will remain downloaded', @@ -3145,7 +4766,11 @@ export const localizations = { brazilian_portuguese: 'O projeto permanecerá baixado', tok_pisin: 'Project i pinis download', indonesian: 'Proyek akan tetap diunduh', - nepali: 'प्रोजेक्ट डाउनलोड भएको रहनेछ' + nepali: 'प्रोजेक्ट डाउनलोड भएको रहनेछ', + hindi: 'परियोजना डाउनलोड की हुई रहेगी', + burmese: 'စီမံကိန်း ဒေါင်းလုဒ်လုပ်ထားသည့် အတိုင်း ရှိနေမည်', + thai: 'โครงการจะยังคงดาวน์โหลดอยู่', + mandarin: '项目将保持已下载状态' }, requestExpiredAttemptsRemaining: { english: @@ -3159,7 +4784,13 @@ export const localizations = { indonesian: 'Permintaan keanggotaan Anda telah kedaluwarsa setelah 7 hari. Anda memiliki {attempts} percobaan{plural} tersisa.', nepali: - 'तपाईंको अनुरोध ७ दिन पछि समाप्त भयो। तपाईंसँग {attempts} प्रयास{plural} बाँकी छ।' + 'तपाईंको अनुरोध ७ दिन पछि समाप्त भयो। तपाईंसँग {attempts} प्रयास{plural} बाँकी छ।', + hindi: + 'आपका अनुरोध 7 दिनों के बाद समाप्त हो गया। आपके पास {attempts} प्रयास{plural} बचे हैं।', + burmese: + 'သင်၏ တောင်းဆိုမှု 7 ရက်အကြာတွင် သက်တမ်းကုန်ဆုံးပါပြီ။ သင့်တွင် {attempts} ကြိုးစားခွင့်{plural} ရှိပါသေးသည်။', + thai: 'คำขอของคุณหมดอายุหลังจาก 7 วัน คุณมีโอกาสอีก {attempts} ครั้ง{plural}', + mandarin: '您的请求在 7 天后已过期。您还有 {attempts} 次{plural}尝试机会。' }, requestExpiredInline: { english: @@ -3173,7 +4804,14 @@ export const localizations = { indonesian: 'Permintaan keanggotaan Anda sebelumnya telah kedaluwarsa setelah 7 hari. Anda memiliki {attempts} percobaan{plural} tersisa.', nepali: - 'तपाईंको अघिल्लो अनुरोध ७ दिन पछि समाप्त भयो। तपाईंसँग {attempts} प्रयास{plural} बाँकी छ।' + 'तपाईंको अघिल्लो अनुरोध ७ दिन पछि समाप्त भयो। तपाईंसँग {attempts} प्रयास{plural} बाँकी छ।', + hindi: + 'आपका पिछला अनुरोध 7 दिनों के बाद समाप्त हो गया। आपके पास {attempts} प्रयास{plural} बचे हैं।', + burmese: + 'သင်၏ ယခင်တောင်းဆိုမှု 7 ရက်အကြာတွင် သက်တမ်းကုန်ဆုံးပါပြီ။ သင့်တွင် {attempts} ကြိုးစားခွင့်{plural} ရှိပါသေးသည်။', + thai: 'คำขอก่อนหน้าของคุณหมดอายุหลังจาก 7 วัน คุณมีโอกาสอีก {attempts} ครั้ง{plural}', + mandarin: + '您之前的请求在 7 天后已过期。您还有 {attempts} 次{plural}尝试机会。' }, requestExpiredNoAttempts: { english: 'Your request expired and you have no more attempts remaining.', @@ -3184,7 +4822,12 @@ export const localizations = { 'Membership request bilong yu i pinis na yu no gat moa chance long attempt.', indonesian: 'Permintaan keanggotaan Anda telah kedaluwarsa dan Anda tidak memiliki percobaan tersisa.', - nepali: 'तपाईंको अनुरोध समाप्त भयो र तपाईंसँग थप प्रयासहरू बाँकी छैन।' + nepali: 'तपाईंको अनुरोध समाप्त भयो र तपाईंसँग थप प्रयासहरू बाँकी छैन।', + hindi: 'आपका अनुरोध समाप्त हो गया और आपके पास कोई और प्रयास नहीं बचे हैं।', + burmese: + 'သင်၏ တောင်းဆိုမှု သက်တမ်းကုန်ဆုံးပါပြီ နှင့် သင့်တွင် ထပ်မံကြိုးစားခွင့် မရှိတော့ပါ။', + thai: 'คำขอของคุณหมดอายุแล้ว และคุณไม่มีโอกาสเหลืออีก', + mandarin: '您的请求已过期,您没有更多尝试机会了。' }, requestExpiredNoAttemptsInline: { english: @@ -3198,7 +4841,13 @@ export const localizations = { indonesian: 'Permintaan keanggotaan Anda sebelumnya telah kedaluwarsa setelah 7 hari dan Anda tidak memiliki percobaan tersisa.', nepali: - 'तपाईंको अघिल्लो अनुरोध ७ दिन पछि समाप्त भयो र तपाईंसँग थप प्रयासहरू बाँकी छैन।' + 'तपाईंको अघिल्लो अनुरोध ७ दिन पछि समाप्त भयो र तपाईंसँग थप प्रयासहरू बाँकी छैन।', + hindi: + 'आपका पिछला अनुरोध 7 दिनों के बाद समाप्त हो गया और आपके पास कोई और प्रयास नहीं बचे हैं।', + burmese: + 'သင်၏ ယခင်တောင်းဆိုမှု 7 ရက်အကြာတွင် သက်တမ်းကုန်ဆုံးပါပြီ နှင့် သင့်တွင် ထပ်မံကြိုးစားခွင့် မရှိတော့ပါ။', + thai: 'คำขอก่อนหน้าของคุณหมดอายุหลังจาก 7 วัน และคุณไม่มีโอกาสเหลืออีก', + mandarin: '您之前的请求在 7 天后已过期,您没有更多尝试机会了。' }, requestPendingInline: { english: @@ -3212,7 +4861,13 @@ export const localizations = { indonesian: 'Permintaan keanggotaan Anda sedang menunggu persetujuan. Anda akan diberitahu ketika sudah diperiksa.', nepali: - 'तपाईंको सदस्यता अनुरोध स्वीकृतिको लागि पर्खिरहेको छ। यसको समीक्षा हुँदा तपाईंलाई सूचित गरिनेछ।' + 'तपाईंको सदस्यता अनुरोध स्वीकृतिको लागि पर्खिरहेको छ। यसको समीक्षा हुँदा तपाईंलाई सूचित गरिनेछ।', + hindi: + 'आपका सदस्यता अनुरोध स्वीकृति के लिए लंबित है। इसकी समीक्षा होने पर आपको सूचित किया जाएगा।', + burmese: + 'သင်၏ အဖွဲ့ဝင်အဖြစ် တောင်းဆိုမှုသည် အတည်ပြုခြင်းအတွက် စောင့်ဆိုင်းနေသည်။ ၎င်းကို စိစစ်သောအခါ သင်အား အကြောင်းကြားပါမည်။', + thai: 'คำขอเป็นสมาชิกของคุณกำลังรอการอนุมัติ คุณจะได้รับการแจ้งเตือนเมื่อมีการตรวจสอบ', + mandarin: '您的成员资格请求正在等待批准。审核时您将收到通知。' }, requestDeclinedInline: { english: @@ -3226,7 +4881,13 @@ export const localizations = { indonesian: 'Permintaan keanggotaan Anda ditolak. Anda memiliki {attempts} percobaan{plural} tersisa.', nepali: - 'तपाईंको अनुरोध अस्वीकार गरियो। तपाईंसँग {attempts} प्रयास{plural} बाँकी छ।' + 'तपाईंको अनुरोध अस्वीकार गरियो। तपाईंसँग {attempts} प्रयास{plural} बाँकी छ।', + hindi: + 'आपका अनुरोध अस्वीकार कर दिया गया था। आपके पास {attempts} प्रयास{plural} बचे हैं।', + burmese: + 'သင်၏ တောင်းဆိုမှုကို ငြင်းဆိုပါသည်။ သင့်တွင် {attempts} ကြိုးစားခွင့်{plural} ရှိပါသေးသည်။', + thai: 'คำขอของคุณถูกปฏิเสธ คุณมีโอกาสอีก {attempts} ครั้ง{plural}', + mandarin: '您的请求已被拒绝。您还有 {attempts} 次{plural}尝试机会。' }, requestDeclinedNoRetryInline: { english: @@ -3238,7 +4899,13 @@ export const localizations = { 'Membership request bilong yu i no na yu no gat moa chance long attempt.', indonesian: 'Permintaan keanggotaan Anda ditolak dan Anda tidak memiliki percobaan tersisa.', - nepali: 'तपाईंको अनुरोध अस्वीकार गरियो र तपाईंसँग थप प्रयासहरू बाँकी छैन।' + nepali: 'तपाईंको अनुरोध अस्वीकार गरियो र तपाईंसँग थप प्रयासहरू बाँकी छैन।', + hindi: + 'आपका अनुरोध अस्वीकार कर दिया गया था और आपके पास कोई और प्रयास नहीं बचे हैं।', + burmese: + 'သင်၏ တောင်းဆိုမှုကို ငြင်းဆိုပါသည် နှင့် သင့်တွင် ထပ်မံကြိုးစားခွင့် မရှိတော့ပါ။', + thai: 'คำขอของคุณถูกปฏิเสธ และคุณไม่มีโอกาสเหลืออีก', + mandarin: '您的请求已被拒绝,您没有更多尝试机会了。' }, requestWithdrawnInline: { english: @@ -3252,7 +4919,13 @@ export const localizations = { indonesian: 'Anda telah menarik permintaan keanggotaan Anda sebelumnya. Anda dapat mengirim permintaan baru kapan saja.', nepali: - 'तपाईंले आफ्नो अघिल्लो अनुरोध फिर्ता लिनुभयो। तपाईं जुनसुकै बेला नयाँ अनुरोध पठाउन सक्नुहुन्छ।' + 'तपाईंले आफ्नो अघिल्लो अनुरोध फिर्ता लिनुभयो। तपाईं जुनसुकै बेला नयाँ अनुरोध पठाउन सक्नुहुन्छ।', + hindi: + 'आपने अपना पिछला अनुरोध वापस ले लिया है। आप किसी भी समय एक नया अनुरोध भेज सकते हैं।', + burmese: + 'သင်သည် သင်၏ ယခင်တောင်းဆိုမှုကို ရုပ်သိမ်းပါပြီ။ သင်သည် မည်သည့်အချိန်တွင်မဆို တောင်းဆိုမှု အသစ်တစ်ခု ပေးပို့နိုင်သည်။', + thai: 'คุณได้ถอนคำขอก่อนหน้าแล้ว คุณสามารถส่งคำขอใหม่ได้ตลอดเวลา', + mandarin: '您已撤回之前的请求。您可以随时发送新请求。' }, viewProject: { english: 'View Project', @@ -3260,7 +4933,11 @@ export const localizations = { brazilian_portuguese: 'Ver Projeto', tok_pisin: 'View Project', indonesian: 'Lihat Proyek', - nepali: 'प्रोजेक्ट हेर्नुहोस्' + nepali: 'प्रोजेक्ट हेर्नुहोस्', + hindi: 'परियोजना देखें', + burmese: 'စီမံကိန်းကို ကြည့်ရှုပါ', + thai: 'ดูโครงการ', + mandarin: '查看项目' }, loadingProjectDetails: { english: 'Loading project details...', @@ -3268,7 +4945,11 @@ export const localizations = { brazilian_portuguese: 'Carregando detalhes do projeto...', tok_pisin: 'Loadim project details...', indonesian: 'Memuat detail proyek...', - nepali: 'प्रोजेक्ट विवरण लोड गर्दै...' + nepali: 'प्रोजेक्ट विवरण लोड गर्दै...', + hindi: 'परियोजना विवरण लोड हो रहे हैं...', + burmese: 'စီမံကိန်း အသေးစိတ်များကို လုပ်ဆောင်နေသည်...', + thai: 'กำลังโหลดรายละเอียดโครงการ...', + mandarin: '正在加载项目详情...' }, onlyOwnersCanInvite: { english: 'Only project owners can invite new members', @@ -3278,7 +4959,11 @@ export const localizations = { 'Apenas proprietários do projeto podem convidar novos membros', tok_pisin: 'Only owner i project i salim member', indonesian: 'Hanya pemilik proyek yang dapat mengundang anggota baru', - nepali: 'प्रोजेक्ट मालिकहरूले मात्र नयाँ सदस्यहरूलाई आमन्त्रित गर्न सक्छन्' + nepali: 'प्रोजेक्ट मालिकहरूले मात्र नयाँ सदस्यहरूलाई आमन्त्रित गर्न सक्छन्', + hindi: 'केवल परियोजना मालिक ही नए सदस्यों को आमंत्रित कर सकते हैं', + burmese: 'စီမံကိန်း ပိုင်ရှင်များသာ အဖွဲ့ဝင်အသစ်များကို ဖိတ်ခေါ်နိုင်သည်', + thai: 'เฉพาะเจ้าของโครงการเท่านั้นที่สามารถเชิญสมาชิกใหม่ได้', + mandarin: '只有项目所有者可以邀请新成员' }, failedToResendInvitation: { english: 'Failed to resend invitation', @@ -3286,7 +4971,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao reenviar convite', tok_pisin: 'I no inap resendim invitation', indonesian: 'Gagal mengirim ulang undangan', - nepali: 'आमन्त्रण पुन: पठाउन असफल' + nepali: 'आमन्त्रण पुन: पठाउन असफल', + hindi: 'आमंत्रण पुनः भेजने में विफल', + burmese: 'ဖိတ်ခေါ်စာကို ပြန်လည် ပေးပို့ရန် မအောင်မြင်ပါ', + thai: 'ส่งคำเชิญอีกครั้งไม่สำเร็จ', + mandarin: '重新发送邀请失败' }, // Restore-related translations restoreAndroidOnly: { @@ -3295,7 +4984,11 @@ export const localizations = { brazilian_portuguese: 'A restauração só está disponível no Android', tok_pisin: 'Restore i pinis long Android', indonesian: 'Pemulihan hanya tersedia di Android', - nepali: 'पुनर्स्थापना एन्ड्रोइडमा मात्र उपलब्ध छ' + nepali: 'पुनर्स्थापना एन्ड्रोइडमा मात्र उपलब्ध छ', + hindi: 'पुनर्स्थापना केवल Android पर उपलब्ध है', + burmese: 'ပြန်လည်ရယူခြင်းသည် Android တွင်သာ ရရှိနိုင်သည်', + thai: 'การกู้คืนมีให้เฉพาะบน Android', + mandarin: '恢复功能仅在 Android 上可用' }, backupAndroidOnly: { english: 'Backup is only available on Android', @@ -3303,7 +4996,11 @@ export const localizations = { brazilian_portuguese: 'O backup só está disponível no Android', tok_pisin: 'Backup i pinis long Android', indonesian: 'Cadangan hanya tersedia di Android', - nepali: 'ब्याकअप एन्ड्रोइडमा मात्र उपलब्ध छ' + nepali: 'ब्याकअप एन्ड्रोइडमा मात्र उपलब्ध छ', + hindi: 'बैकअप केवल Android पर उपलब्ध है', + burmese: 'အရန်သိမ်းဆည်းခြင်းသည် Android တွင်သာ ရရှိနိုင်သည်', + thai: 'การสำรองข้อมูลมีให้เฉพาะบน Android', + mandarin: '备份功能仅在 Android 上可用' }, permissionDenied: { english: 'Permission Denied', @@ -3311,7 +5008,11 @@ export const localizations = { brazilian_portuguese: 'Permissão Negada', tok_pisin: 'Permission i no', indonesian: 'Izin Ditolak', - nepali: 'अनुमति अस्वीकृत' + nepali: 'अनुमति अस्वीकृत', + hindi: 'अनुमति अस्वीकृत', + burmese: 'ခွင့်ပြုချက် ငြင်းဆိုပါသည်', + thai: 'ปฏิเสธการอนุญาต', + mandarin: '权限被拒绝' }, grantMicrophonePermission: { english: 'Grant Microphone Permission', @@ -3319,7 +5020,11 @@ export const localizations = { brazilian_portuguese: 'Conceder Permissão de Microfone', tok_pisin: 'Grant Microphone Permission', indonesian: 'Mengakses Mikrofon', - nepali: 'माइक्रोफोन अनुमति दिनुहोस्' + nepali: 'माइक्रोफोन अनुमति दिनुहोस्', + hindi: 'माइक्रोफोन अनुमति दें', + burmese: 'မိုက်ခရိုဖုန်း ခွင့်ပြုချက် ပေးပါ', + thai: 'ให้สิทธิ์ไมโครโฟน', + mandarin: '授予麦克风权限' }, autoCalibrate: { english: 'Auto-Calibrate', @@ -3327,7 +5032,11 @@ export const localizations = { brazilian_portuguese: 'Auto-Calibrar', tok_pisin: 'Olsem wanem yet', indonesian: 'Auto-Kalibrasi', - nepali: 'स्वत: क्यालिब्रेट' + nepali: 'स्वत: क्यालिब्रेट', + hindi: 'स्वचालित कैलिब्रेट', + burmese: 'အလိုအလျောက် စစ်ဆေးခြင်း', + thai: 'ปรับแต่งอัตโนมัติ', + mandarin: '自动校准' }, calibrateMicrophone: { english: 'Calibrate your microphone', @@ -3335,7 +5044,11 @@ export const localizations = { brazilian_portuguese: 'Calibre seu microfone', tok_pisin: 'Stretim mikrofon bilong yu', indonesian: 'Kalibrasi mikrofon Anda', - nepali: 'आफ्नो माइक्रोफोन क्यालिब्रेट गर्नुहोस्' + nepali: 'आफ्नो माइक्रोफोन क्यालिब्रेट गर्नुहोस्', + hindi: 'अपना माइक्रोफोन कैलिब्रेट करें', + burmese: 'သင်၏ မိုက်ခရိုဖုန်းကို စစ်ဆေးပါ', + thai: 'ปรับแต่งไมโครโฟนของคุณ', + mandarin: '校准您的麦克风' }, calibrateMicrophoneDescription: { english: 'Let us automatically adjust the sensitivity for your environment', @@ -3348,7 +5061,13 @@ export const localizations = { indonesian: 'Biarkan kami secara otomatis menyesuaikan sensitivitas untuk lingkungan Anda', nepali: - 'तपाईंको वातावरणको लागि स्वचालित रूपमा संवेदनशीलता समायोजन गर्न दिनुहोस्' + 'तपाईंको वातावरणको लागि स्वचालित रूपमा संवेदनशीलता समायोजन गर्न दिनुहोस्', + hindi: + 'हमें आपके वातावरण के लिए स्वचालित रूप से संवेदनशीलता समायोजित करने दें', + burmese: + 'သင်၏ ပတ်ဝန်းကျင်အတွက် အာရုံခံနိုင်စွမ်းကို အလိုအလျောက် ညှိပေးရန် ခွင့်ပြုပါ', + thai: 'ให้เราปรับความไวให้เหมาะกับสภาพแวดล้อมของคุณโดยอัตโนมัติ', + mandarin: '让我们自动调整您环境的灵敏度' }, skip: { english: 'Skip', @@ -3356,7 +5075,11 @@ export const localizations = { brazilian_portuguese: 'Pular', tok_pisin: 'Lusim', indonesian: 'Lewati', - nepali: 'छोड्नुहोस्' + nepali: 'छोड्नुहोस्', + hindi: 'छोड़ें', + burmese: 'ကျော်သွားပါ', + thai: 'ข้าม', + mandarin: '跳过' }, confirmAudioRestore: { english: 'Confirm Audio Restore', @@ -3364,7 +5087,11 @@ export const localizations = { brazilian_portuguese: 'Confirmar Restauração de Áudio', tok_pisin: 'Confirm Audio Restore', indonesian: 'Konfirmasi Pemulihan Audio', - nepali: 'अडियो पुनर्स्थापना पुष्टि गर्नुहोस्' + nepali: 'अडियो पुनर्स्थापना पुष्टि गर्नुहोस्', + hindi: 'ऑडियो पुनर्स्थापना की पुष्टि करें', + burmese: 'အသံ ပြန်လည်ရယူခြင်းကို အတည်ပြုပါ', + thai: 'ยืนยันการกู้คืนเสียง', + mandarin: '确认音频恢复' }, confirmAudioRestoreMessage: { english: 'This will restore your audio files from the backup. Continue?', @@ -3375,7 +5102,12 @@ export const localizations = { tok_pisin: 'This i restore audio file bilong backup. Continue?', indonesian: 'Ini akan memulihkan file audio Anda dari cadangan. Lanjutkan?', nepali: - 'यसले तपाईंको अडियो फाइलहरू ब्याकअपबाट पुनर्स्थापित गर्नेछ। जारी राख्ने?' + 'यसले तपाईंको अडियो फाइलहरू ब्याकअपबाट पुनर्स्थापित गर्नेछ। जारी राख्ने?', + hindi: 'यह आपकी ऑडियो फ़ाइलों को बैकअप से पुनर्स्थापित करेगा। जारी रखें?', + burmese: + '၎င်းသည် သင်၏ အသံဖိုင်များကို အရန်သိမ်းဆည်းမှုမှ ပြန်လည်ရယူမည်။ ဆက်လုပ်မည်လား?', + thai: 'นี่จะกู้คืนไฟล์เสียงของคุณจากสำรองข้อมูล ดำเนินการต่อหรือไม่?', + mandarin: '这将从备份中恢复您的音频文件。继续吗?' }, restoreAudioOnly: { english: 'Restore Audio', @@ -3383,7 +5115,11 @@ export const localizations = { brazilian_portuguese: 'Restaurar Áudio', tok_pisin: 'Restore Audio', indonesian: 'Pemulihan Audio', - nepali: 'अडियो पुनर्स्थापना गर्नुहोस्' + nepali: 'अडियो पुनर्स्थापना गर्नुहोस्', + hindi: 'ऑडियो पुनर्स्थापित करें', + burmese: 'အသံ ပြန်လည်ရယူပါ', + thai: 'กู้คืนเสียง', + mandarin: '恢复音频' }, failedRestore: { english: 'Failed to restore: {error}', @@ -3391,7 +5127,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao restaurar: {error}', tok_pisin: 'I no inap restore: {error}', indonesian: 'Gagal memulihkan: {error}', - nepali: 'पुनर्स्थापना गर्न असफल: {error}' + nepali: 'पुनर्स्थापना गर्न असफल: {error}', + hindi: 'पुनर्स्थापना करने में विफल: {error}', + burmese: 'ပြန်လည်ရယူရန် မအောင်မြင်ပါ: {error}', + thai: 'กู้คืนไม่สำเร็จ: {error}', + mandarin: '恢复失败: {error}' }, restoreCompleteBase: { english: @@ -3405,7 +5145,14 @@ export const localizations = { indonesian: 'Pemulihan selesai: {audioCopied} file audio disalin, {audioSkippedDueToError} dilewatkan karena kesalahan', nepali: - 'पुनर्स्थापना पूरा भयो: {audioCopied} अडियो फाइलहरू कपी गरियो, {audioSkippedDueToError} त्रुटिहरूका कारण छोडियो' + 'पुनर्स्थापना पूरा भयो: {audioCopied} अडियो फाइलहरू कपी गरियो, {audioSkippedDueToError} त्रुटिहरूका कारण छोडियो', + hindi: + 'पुनर्स्थापना पूर्ण: {audioCopied} ऑडियो फ़ाइलें कॉपी की गईं, {audioSkippedDueToError} त्रुटियों के कारण छोड़ दी गईं', + burmese: + 'ပြန်လည်ရယူခြင်း ပြီးစီးပါပြီ: {audioCopied} အသံဖိုင်များ ကူးယူပြီးပါပြီ၊ {audioSkippedDueToError} ကို အမှားများကြောင့် ကျော်သွားပါသည်', + thai: 'กู้คืนเสร็จสิ้น: คัดลอกไฟล์เสียง {audioCopied} ไฟล์ ข้าม {audioSkippedDueToError} ไฟล์เนื่องจากข้อผิดพลาด', + mandarin: + '恢复完成: 已复制 {audioCopied} 个音频文件,由于错误跳过了 {audioSkippedDueToError} 个' }, restoreSkippedLocallyPart: { english: ', {audioSkippedLocally} skipped (already exists)', @@ -3413,7 +5160,12 @@ export const localizations = { brazilian_portuguese: ', {audioSkippedLocally} ignorados (já existem)', tok_pisin: ', {audioSkippedLocally} i skip long local.', indonesian: ', {audioSkippedLocally} dilewatkan (sudah ada)', - nepali: ', {audioSkippedLocally} छोडियो (पहिले नै अवस्थित छ)' + nepali: ', {audioSkippedLocally} छोडियो (पहिले नै अवस्थित छ)', + hindi: ', {audioSkippedLocally} छोड़ दिया गया (पहले से मौजूद है)', + burmese: + ', {audioSkippedLocally} ကို ကျော်သွားပါသည် (အရင်ကတည်းက ရှိနေပါသည်)', + thai: ', ข้าม {audioSkippedLocally} ไฟล์ (มีอยู่แล้ว)', + mandarin: ',跳过了 {audioSkippedLocally} 个(已存在)' }, restoreCompleteTitle: { english: 'Restore Complete', @@ -3421,7 +5173,11 @@ export const localizations = { brazilian_portuguese: 'Restauração Concluída', tok_pisin: 'Restore Complete', indonesian: 'Pemulihan Selesai', - nepali: 'पुनर्स्थापना पूरा भयो' + nepali: 'पुनर्स्थापना पूरा भयो', + hindi: 'पुनर्स्थापना पूर्ण', + burmese: 'ပြန်လည်ရယူခြင်း ပြီးစီးပါပြီ', + thai: 'กู้คืนเสร็จสิ้น', + mandarin: '恢复完成' }, restoreFailedTitle: { english: 'Restore Failed: {error}', @@ -3429,7 +5185,11 @@ export const localizations = { brazilian_portuguese: 'Restauração Falhou: {error}', tok_pisin: 'Restore i no: {error}', indonesian: 'Pemulihan Gagal: {error}', - nepali: 'पुनर्स्थापना असफल: {error}' + nepali: 'पुनर्स्थापना असफल: {error}', + hindi: 'पुनर्स्थापना विफल: {error}', + burmese: 'ပြန်လည်ရယူရန် မအောင်မြင်ပါ: {error}', + thai: 'กู้คืนไม่สำเร็จ: {error}', + mandarin: '恢复失败: {error}' }, projectInvitationTitle: { english: 'Project Invitation', @@ -3437,7 +5197,11 @@ export const localizations = { brazilian_portuguese: 'Convite para o Projeto', tok_pisin: 'Project Invitation', indonesian: 'Undangan Proyek', - nepali: 'प्रोजेक्ट आमन्त्रण' + nepali: 'प्रोजेक्ट आमन्त्रण', + hindi: 'परियोजना आमंत्रण', + burmese: 'စီမံကိန်း ဖိတ်ခေါ်စာ', + thai: 'คำเชิญโครงการ', + mandarin: '项目邀请' }, joinRequestTitle: { english: 'Join Request', @@ -3445,7 +5209,11 @@ export const localizations = { brazilian_portuguese: 'Solicitação de Adesão', tok_pisin: 'Join Request', indonesian: 'Permintaan Bergabung', - nepali: 'सामेल हुने अनुरोध' + nepali: 'सामेल हुने अनुरोध', + hindi: 'शामिल होने का अनुरोध', + burmese: 'ပါဝင်ရန် တောင်းဆိုမှု', + thai: 'คำขอเข้าร่วม', + mandarin: '加入请求' }, invitedYouToJoin: { english: '{sender} invited you to join "{project}" as {role}', @@ -3457,7 +5225,13 @@ export const localizations = { indonesian: '{sender} mengundang Anda untuk bergabung dengan proyek "{project}" sebagai {role}', nepali: - '{sender} ले तपाईंलाई "{project}" मा {role} को रूपमा सामेल हुन आमन्त्रित गर्नुभयो' + '{sender} ले तपाईंलाई "{project}" मा {role} को रूपमा सामेल हुन आमन्त्रित गर्नुभयो', + hindi: + '{sender} ने आपको "{project}" में {role} के रूप में शामिल होने के लिए आमंत्रित किया है', + burmese: + '{sender} သည် သင့်အား "{project}" စီမံကိန်းတွင် {role} အဖြစ် ပါဝင်ရန် ဖိတ်ခေါ်ပါသည်', + thai: '{sender} ได้เชิญคุณเข้าร่วม "{project}" ในฐานะ {role}', + mandarin: '{sender} 已邀请您以 {role} 身份加入 "{project}"' }, requestedToJoin: { english: '{sender} requested to join "{project}" as {role}', @@ -3469,7 +5243,13 @@ export const localizations = { indonesian: '{sender} meminta untuk bergabung dengan proyek "{project}" sebagai {role}', nepali: - '{sender} ले "{project}" मा {role} को रूपमा सामेल हुन अनुरोध गर्नुभयो' + '{sender} ले "{project}" मा {role} को रूपमा सामेल हुन अनुरोध गर्नुभयो', + hindi: + '{sender} ने "{project}" में {role} के रूप में शामिल होने का अनुरोध किया है', + burmese: + '{sender} သည် "{project}" စီမံကိန်းတွင် {role} အဖြစ် ပါဝင်ရန် တောင်းဆိုပါသည်', + thai: '{sender} ได้ขอเข้าร่วม "{project}" ในฐานะ {role}', + mandarin: '{sender} 已请求以 {role} 身份加入 "{project}"' }, downloadProjectLabel: { english: 'Download Project', @@ -3477,7 +5257,11 @@ export const localizations = { brazilian_portuguese: 'Baixar Projeto', tok_pisin: 'Download Project', indonesian: 'Unduh Proyek', - nepali: 'प्रोजेक्ट डाउनलोड गर्नुहोस्' + nepali: 'प्रोजेक्ट डाउनलोड गर्नुहोस्', + hindi: 'परियोजना डाउनलोड करें', + burmese: 'စီမံကိန်း ဒေါင်းလုဒ်လုပ်ပါ', + thai: 'ดาวน์โหลดโครงการ', + mandarin: '下载项目' }, projectNotAvailableOfflineWarning: { english: 'Project will not be available offline without download', @@ -3485,7 +5269,12 @@ export const localizations = { brazilian_portuguese: 'O projeto não estará disponíel offline sem download', tok_pisin: 'Project i no pinis long download', indonesian: 'Proyek tidak akan tersedia secara offline tanpa unduhan', - nepali: 'डाउनलोड बिना प्रोजेक्ट अफलाइन उपलब्ध हुने छैन' + nepali: 'डाउनलोड बिना प्रोजेक्ट अफलाइन उपलब्ध हुने छैन', + hindi: 'डाउनलोड के बिना परियोजना ऑफलाइन उपलब्ध नहीं होगी', + burmese: + 'ဒေါင်းလုဒ်လုပ်ခြင်းမရှိဘဲ စီမံကိန်းသည် အော့ဖ်လိုင်းတွင် ရရှိနိုင်မည် မဟုတ်ပါ', + thai: 'โครงการจะไม่พร้อมใช้งานแบบออฟไลน์หากไม่ดาวน์โหลด', + mandarin: '不下载项目将无法离线使用' }, noNotificationsTitle: { english: 'No Notifications', @@ -3493,7 +5282,11 @@ export const localizations = { brazilian_portuguese: 'Sem Notificações', tok_pisin: 'No Notification', indonesian: 'Tidak Ada Notifikasi', - nepali: 'कुनै सूचना छैन' + nepali: 'कुनै सूचना छैन', + hindi: 'कोई सूचनाएं नहीं', + burmese: 'အကြောင်းကြားချက် မရှိပါ', + thai: 'ไม่มีการแจ้งเตือน', + mandarin: '无通知' }, noNotificationsMessage: { english: "You'll see project invitations and join requests here", @@ -3505,7 +5298,12 @@ export const localizations = { indonesian: 'Anda akan melihat undangan ke proyek dan permintaan bergabung di sini', nepali: - 'तपाईंले यहाँ प्रोजेक्ट आमन्त्रणहरू र सामेल हुने अनुरोधहरू देख्नुहुनेछ' + 'तपाईंले यहाँ प्रोजेक्ट आमन्त्रणहरू र सामेल हुने अनुरोधहरू देख्नुहुनेछ', + hindi: 'आप यहां परियोजना आमंत्रण और शामिल होने के अनुरोध देखेंगे', + burmese: + 'သင်သည် ဤနေရာတွင် စီမံကိန်း ဖိတ်ခေါ်စာများ နှင့် ပါဝင်ရန် တောင်းဆိုမှုများကို မြင်ရမည်', + thai: 'คุณจะเห็นคำเชิญโครงการและคำขอเข้าร่วมที่นี่', + mandarin: '您将在此处看到项目邀请和加入请求' }, invitationAcceptedSuccessfully: { english: 'Invitation accepted successfully', @@ -3513,7 +5311,11 @@ export const localizations = { brazilian_portuguese: 'Convite aceito com sucesso', tok_pisin: 'Invitation i accept gut', indonesian: 'Undangan diterima dengan sukses', - nepali: 'आमन्त्रण सफलतापूर्वक स्वीकार गरियो' + nepali: 'आमन्त्रण सफलतापूर्वक स्वीकार गरियो', + hindi: 'आमंत्रण सफलतापूर्वक स्वीकार कर लिया गया', + burmese: 'ဖိတ်ခေါ်စာ အောင်မြင်စွာ လက်ခံပါပြီ', + thai: 'ยอมรับคำเชิญสำเร็จ', + mandarin: '邀请已成功接受' }, invitationDeclinedSuccessfully: { english: 'Invitation declined', @@ -3521,15 +5323,11 @@ export const localizations = { brazilian_portuguese: 'Convite recusado', tok_pisin: 'Invitation i no', indonesian: 'Undangan ditolak', - nepali: 'आमन्त्रण अस्वीकृत' - }, - mustBeOnlineToAcceptInvite: { - english: 'You must be online to accept an invitation', - spanish: 'Debes estar en línea para aceptar una invitación', - brazilian_portuguese: 'Você precisa estar online para aceitar um convite', - tok_pisin: 'Yu mas stap long internet bilong accept invitation', - indonesian: 'Anda harus online untuk menerima undangan', - nepali: 'आमन्त्रण स्वीकार गर्न तपाईं अनलाइनमा हुनुपर्छ' + nepali: 'आमन्त्रण अस्वीकृत', + hindi: 'आमंत्रण अस्वीकार कर दिया गया', + burmese: 'ဖိတ်ခေါ်စာ ငြင်းဆိုပါသည်', + thai: 'ปฏิเสธคำเชิญ', + mandarin: '邀请已拒绝' }, failedToAcceptInvite: { english: 'Failed to accept invitation', @@ -3537,7 +5335,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao aceitar convite', tok_pisin: 'I no inap accept invitation', indonesian: 'Gagal menerima undangan', - nepali: 'आमन्त्रण स्वीकार गर्न असफल' + nepali: 'आमन्त्रण स्वीकार गर्न असफल', + hindi: 'आमंत्रण स्वीकार करने में विफल', + burmese: 'ဖိတ်ခေါ်စာကို လက်ခံရန် မအောင်မြင်ပါ', + thai: 'ยอมรับคำเชิญไม่สำเร็จ', + mandarin: '接受邀请失败' }, failedToDeclineInvite: { english: 'Failed to decline invitation', @@ -3545,7 +5347,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao recusar convite', tok_pisin: 'I no inap decline invitation', indonesian: 'Gagal menolak undangan', - nepali: 'आमन्त्रण अस्वीकार गर्न असफल' + nepali: 'आमन्त्रण अस्वीकार गर्न असफल', + hindi: 'आमंत्रण अस्वीकार करने में विफल', + burmese: 'ဖိတ်ခေါ်စာကို ငြင်းဆိုရန် မအောင်မြင်ပါ', + thai: 'ปฏิเสธคำเชิญไม่สำเร็จ', + mandarin: '拒绝邀请失败' }, invitationAcceptedDownloadFailed: { english: 'Invitation accepted but download failed', @@ -3553,7 +5359,11 @@ export const localizations = { brazilian_portuguese: 'Convite aceito mas o download falhou', tok_pisin: 'Invitation i accept but i no inap download', indonesian: 'Undangan diterima tapi unduhan gagal', - nepali: 'आमन्त्रण स्वीकार गरियो तर डाउनलोड असफल भयो' + nepali: 'आमन्त्रण स्वीकार गरियो तर डाउनलोड असफल भयो', + hindi: 'आमंत्रण स्वीकार कर लिया गया लेकिन डाउनलोड विफल रहा', + burmese: 'ဖိတ်ခေါ်စာ လက်ခံပါပြီ သို့သော် ဒေါင်းလုဒ်လုပ်ရန် မအောင်မြင်ပါ', + thai: 'ยอมรับคำเชิญแล้ว แต่การดาวน์โหลดล้มเหลว', + mandarin: '邀请已接受,但下载失败' }, unknownProject: { english: 'Unknown Project', @@ -3561,7 +5371,11 @@ export const localizations = { brazilian_portuguese: 'Projeto Desconhecido', tok_pisin: 'Unknown Project', indonesian: 'Proyek Tidak Dikenal', - nepali: 'अज्ञात प्रोजेक्ट' + nepali: 'अज्ञात प्रोजेक्ट', + hindi: 'अज्ञात परियोजना', + burmese: 'မသိသော စီမံကိန်း', + thai: 'โครงการที่ไม่รู้จัก', + mandarin: '未知项目' }, ownerRole: { english: 'owner', @@ -3569,7 +5383,11 @@ export const localizations = { brazilian_portuguese: 'proprietário', tok_pisin: 'owner', indonesian: 'pemilik', - nepali: 'मालिक' + nepali: 'मालिक', + hindi: 'मालिक', + burmese: 'ပိုင်ရှင်', + thai: 'เจ้าของ', + mandarin: '所有者' }, memberRole: { english: 'member', @@ -3577,7 +5395,11 @@ export const localizations = { brazilian_portuguese: 'membro', tok_pisin: 'member', indonesian: 'anggota', - nepali: 'सदस्य' + nepali: 'सदस्य', + hindi: 'सदस्य', + burmese: 'အဖွဲ့ဝင်', + thai: 'สมาชิก', + mandarin: '成员' }, offlineNotificationMessage: { english: @@ -3591,7 +5413,13 @@ export const localizations = { indonesian: 'Anda sedang offline. Perubahan apa pun yang Anda buat akan disinkronkan ketika Anda kembali online.', nepali: - 'तपाईं अफलाइन हुनुहुन्छ। तपाईंले गर्नुभएका कुनै पनि परिवर्तनहरू तपाईं अनलाइन फर्किँदा सिङ्क हुनेछन्।' + 'तपाईं अफलाइन हुनुहुन्छ। तपाईंले गर्नुभएका कुनै पनि परिवर्तनहरू तपाईं अनलाइन फर्किँदा सिङ्क हुनेछन्।', + hindi: + 'आप ऑफलाइन हैं। आपके द्वारा किए गए कोई भी परिवर्तन तब सिंक होंगे जब आप वापस ऑनलाइन होंगे।', + burmese: + 'သင်သည် အော့ဖ်လိုင်း ဖြစ်နေသည်။ သင်ပြုလုပ်သော မည်သည့် ပြောင်းလဲမှုများကိုမဆို သင်ပြန်လည် အွန်လိုင်းဖြစ်သောအခါ အင်ချုန်းလုပ်ပါမည်။', + thai: 'คุณออฟไลน์อยู่ การเปลี่ยนแปลงใดๆ ที่คุณทำจะซิงค์เมื่อคุณกลับมาออนไลน์', + mandarin: '您处于离线状态。您所做的任何更改将在您重新上线时同步。' }, filesDownloaded: { english: 'files downloaded', @@ -3599,7 +5427,11 @@ export const localizations = { brazilian_portuguese: 'arquivos baixados', tok_pisin: 'ol fail i download pinis', indonesian: 'file diunduh', - nepali: 'फाइलहरू डाउनलोड भयो' + nepali: 'फाइलहरू डाउनलोड भयो', + hindi: 'फ़ाइलें डाउनलोड की गईं', + burmese: 'ဖိုင်များ ဒေါင်းလုဒ်လုပ်ပြီးပါပြီ', + thai: 'ดาวน์โหลดไฟล์แล้ว', + mandarin: '文件已下载' }, downloading: { english: 'downloading', @@ -3607,7 +5439,11 @@ export const localizations = { brazilian_portuguese: 'baixando', tok_pisin: 'i download nau', indonesian: 'mengunduh', - nepali: 'डाउनलोड गर्दै' + nepali: 'डाउनलोड गर्दै', + hindi: 'डाउनलोड हो रहा है', + burmese: 'ဒေါင်းလုဒ်လုပ်နေသည်', + thai: 'กำลังดาวน์โหลด', + mandarin: '正在下载' }, uploading: { english: 'uploading', @@ -3615,7 +5451,11 @@ export const localizations = { brazilian_portuguese: 'enviando', tok_pisin: 'i upload nau', indonesian: 'mengunggah', - nepali: 'अपलोड गर्दै' + nepali: 'अपलोड गर्दै', + hindi: 'अपलोड हो रहा है', + burmese: 'အပ်လုဒ်လုပ်နေသည်', + thai: 'กำลังอัปโหลด', + mandarin: '正在上传' }, files: { english: 'files', @@ -3623,7 +5463,11 @@ export const localizations = { brazilian_portuguese: 'arquivos', tok_pisin: 'ol fail', indonesian: 'file', - nepali: 'फाइलहरू' + nepali: 'फाइलहरू', + hindi: 'फ़ाइलें', + burmese: 'ဖိုင်များ', + thai: 'ไฟล์', + mandarin: '文件' }, syncingDatabase: { english: 'syncing database', @@ -3631,7 +5475,11 @@ export const localizations = { brazilian_portuguese: 'sincronizando banco de dados', tok_pisin: 'i sync database nau', indonesian: 'mengosinkronkan basis data', - nepali: 'डाटाबेस सिङ्क गर्दै' + nepali: 'डाटाबेस सिङ्क गर्दै', + hindi: 'डेटाबेस सिंक हो रहा है', + burmese: 'ဒေတာဘေ့စ် အင်ချုန်းလုပ်နေသည်', + thai: 'กำลังซิงค์ฐานข้อมูล', + mandarin: '正在同步数据库' }, lastSync: { english: 'last sync', @@ -3639,7 +5487,11 @@ export const localizations = { brazilian_portuguese: 'última sincronização', tok_pisin: 'las sync', indonesian: 'sinkron terakhir', - nepali: 'अन्तिम सिङ्क' + nepali: 'अन्तिम सिङ्क', + hindi: 'अंतिम सिंक', + burmese: 'နောက်ဆုံး အင်ချုန်းလုပ်ခြင်း', + thai: 'ซิงค์ล่าสุด', + mandarin: '上次同步' }, never: { english: 'Never', @@ -3647,7 +5499,11 @@ export const localizations = { brazilian_portuguese: 'Nunca', tok_pisin: 'Nogat', indonesian: 'Tidak pernah', - nepali: 'कहिल्यै होइन' + nepali: 'कहिल्यै होइन', + hindi: 'कभी नहीं', + burmese: 'မည်သည့်အခါမျှ', + thai: 'ไม่เคย', + mandarin: '从不' }, unknown: { english: 'unknown', @@ -3655,7 +5511,11 @@ export const localizations = { brazilian_portuguese: 'desconhecido', tok_pisin: 'mi no save', indonesian: 'tidak dikenal', - nepali: 'अज्ञात' + nepali: 'अज्ञात', + hindi: 'अज्ञात', + burmese: 'မသိသော', + thai: 'ไม่ทราบ', + mandarin: '未知' }, notSynced: { english: 'not synced', @@ -3663,7 +5523,11 @@ export const localizations = { brazilian_portuguese: 'não sincronizado', tok_pisin: 'i no sync yet', indonesian: 'tidak disinkronkan', - nepali: 'सिङ्क भएको छैन' + nepali: 'सिङ्क भएको छैन', + hindi: 'सिंक नहीं हुआ', + burmese: 'အင်ချုန်းလုပ်မထားသေးပါ', + thai: 'ยังไม่ได้ซิงค์', + mandarin: '未同步' }, connecting: { english: 'connecting', @@ -3671,7 +5535,11 @@ export const localizations = { brazilian_portuguese: 'conectando', tok_pisin: 'i try long connect', indonesian: 'menghubungkan', - nepali: 'जडान गर्दै' + nepali: 'जडान गर्दै', + hindi: 'कनेक्ट हो रहा है', + burmese: 'ချိတ်ဆက်နေသည်', + thai: 'กำลังเชื่อมต่อ', + mandarin: '正在连接' }, disconnected: { english: 'disconnected', @@ -3679,7 +5547,11 @@ export const localizations = { brazilian_portuguese: 'desconectado', tok_pisin: 'i no connect', indonesian: 'terputus', - nepali: 'विच्छेद भयो' + nepali: 'विच्छेद भयो', + hindi: 'डिस्कनेक्ट हो गया', + burmese: 'ချိတ်ဆက်မှု ပြတ်တောက်ပါပြီ', + thai: 'ตัดการเชื่อมต่อ', + mandarin: '已断开连接' }, syncingAttachments: { english: 'syncing attachments', @@ -3687,7 +5559,11 @@ export const localizations = { brazilian_portuguese: 'sincronizando anexos', tok_pisin: 'i sync ol attachment', indonesian: 'mengosinkronkan lampiran', - nepali: 'संलग्नकहरू सिङ्क गर्दै' + nepali: 'संलग्नकहरू सिङ्क गर्दै', + hindi: 'संलग्नक सिंक हो रहे हैं', + burmese: 'ပူးတွဲဖိုင်များ အင်ချုန်းလုပ်နေသည်', + thai: 'กำลังซิงค์ไฟล์แนบ', + mandarin: '正在同步附件' }, attachmentSync: { english: 'attachment sync', @@ -3695,7 +5571,11 @@ export const localizations = { brazilian_portuguese: 'sincronização de anexos', tok_pisin: 'attachment sync', indonesian: 'sinkron lampiran', - nepali: 'संलग्नक सिङ्क' + nepali: 'संलग्नक सिङ्क', + hindi: 'संलग्नक सिंक', + burmese: 'ပူးတွဲဖိုင်များ အင်ချုန်းလုပ်ခြင်း', + thai: 'การซิงค์ไฟล์แนบ', + mandarin: '附件同步' }, databaseSyncError: { english: 'database sync error', @@ -3703,7 +5583,11 @@ export const localizations = { brazilian_portuguese: 'erro de sincronização de banco de dados', tok_pisin: 'database sync i gat problem', indonesian: 'kesalahan sinkron basis data', - nepali: 'डाटाबेस सिङ्क त्रुटि' + nepali: 'डाटाबेस सिङ्क त्रुटि', + hindi: 'डेटाबेस सिंक त्रुटि', + burmese: 'ဒေတာဘေ့စ် အင်ချုန်းလုပ်ခြင်း အမှား', + thai: 'ข้อผิดพลาดในการซิงค์ฐานข้อมูล', + mandarin: '数据库同步错误' }, attachmentSyncError: { english: 'attachment sync error', @@ -3711,7 +5595,11 @@ export const localizations = { brazilian_portuguese: 'erro de sincronização de anexos', tok_pisin: 'attachment sync i gat problem', indonesian: 'kesalahan sinkron lampiran', - nepali: 'संलग्नक सिङ्क त्रुटि' + nepali: 'संलग्नक सिङ्क त्रुटि', + hindi: 'संलग्नक सिंक त्रुटि', + burmese: 'ပူးတွဲဖိုင်များ အင်ချုန်းလုပ်ခြင်း အမှား', + thai: 'ข้อผิดพลาดในการซิงค์ไฟล์แนบ', + mandarin: '附件同步错误' }, uploadingData: { english: 'uploading data', @@ -3719,7 +5607,11 @@ export const localizations = { brazilian_portuguese: 'enviando dados', tok_pisin: 'i upload data', indonesian: 'mengunggah data', - nepali: 'डाटा अपलोड गर्दै' + nepali: 'डाटा अपलोड गर्दै', + hindi: 'डेटा अपलोड हो रहा है', + burmese: 'ဒေတာ အပ်လုဒ်လုပ်နေသည်', + thai: 'กำลังอัปโหลดข้อมูล', + mandarin: '正在上传数据' }, downloadingData: { english: 'downloading data', @@ -3727,7 +5619,11 @@ export const localizations = { brazilian_portuguese: 'baixando dados', tok_pisin: 'i download data', indonesian: 'mengunduh data', - nepali: 'डाटा डाउनलोड गर्दै' + nepali: 'डाटा डाउनलोड गर्दै', + hindi: 'डेटा डाउनलोड हो रहा है', + burmese: 'ဒေတာ ဒေါင်းလုဒ်လုပ်နေသည်', + thai: 'กำลังดาวน์โหลดข้อมูล', + mandarin: '正在下载数据' }, syncError: { english: 'sync error', @@ -3735,7 +5631,11 @@ export const localizations = { brazilian_portuguese: 'erro de sincronização', tok_pisin: 'sync i gat problem', indonesian: 'kesalahan sinkron', - nepali: 'सिङ्क त्रुटि' + nepali: 'सिङ्क त्रुटि', + hindi: 'सिंक त्रुटि', + burmese: 'အင်ချုန်းလုပ်ခြင်း အမှား', + thai: 'ข้อผิดพลาดในการซิงค์', + mandarin: '同步错误' }, tapForDetails: { english: 'tap for details', @@ -3743,7 +5643,11 @@ export const localizations = { brazilian_portuguese: 'toque para detalhes', tok_pisin: 'presim long lukim moa', indonesian: 'ketuk untuk detail', - nepali: 'विवरणको लागि ट्याप गर्नुहोस्' + nepali: 'विवरणको लागि ट्याप गर्नुहोस्', + hindi: 'विवरण के लिए टैप करें', + burmese: 'အသေးစိတ်များအတွက် ထိပါ', + thai: 'แตะเพื่อดูรายละเอียด', + mandarin: '点击查看详情' }, downloadComplete: { english: 'download complete', @@ -3751,7 +5655,11 @@ export const localizations = { brazilian_portuguese: 'download completo', tok_pisin: 'download i pinis', indonesian: 'unduhan selesai', - nepali: 'डाउनलोड पूरा भयो' + nepali: 'डाउनलोड पूरा भयो', + hindi: 'डाउनलोड पूर्ण', + burmese: 'ဒေါင်းလုဒ်လုပ်ခြင်း ပြီးစီးပါပြီ', + thai: 'ดาวน์โหลดเสร็จสิ้น', + mandarin: '下载完成' }, queued: { english: 'queued', @@ -3759,7 +5667,11 @@ export const localizations = { brazilian_portuguese: 'em fila', tok_pisin: 'i wet long lain', indonesian: 'dalam antrian', - nepali: 'पङ्क्तिमा छ' + nepali: 'पङ्क्तिमा छ', + hindi: 'कतार में', + burmese: 'တန်းစီထားသည်', + thai: 'อยู่ในคิว', + mandarin: '已排队' }, queuedForDownload: { english: 'queued for download', @@ -3767,7 +5679,11 @@ export const localizations = { brazilian_portuguese: 'em fila para baixar', tok_pisin: 'i wet long lain long download', indonesian: 'dalam antrian untuk unduhan', - nepali: 'डाउनलोडको लागि पङ्क्तिमा' + nepali: 'डाउनलोडको लागि पङ्क्तिमा', + hindi: 'डाउनलोड के लिए कतार में', + burmese: 'ဒေါင်းလုဒ်လုပ်ရန် တန်းစီထားသည်', + thai: 'อยู่ในคิวสำหรับการดาวน์โหลด', + mandarin: '已排队等待下载' }, complete: { english: 'complete', @@ -3775,7 +5691,11 @@ export const localizations = { brazilian_portuguese: 'completo', tok_pisin: 'pinis', indonesian: 'selesai', - nepali: 'पूरा भयो' + nepali: 'पूरा भयो', + hindi: 'पूर्ण', + burmese: 'ပြီးစီးပါပြီ', + thai: 'เสร็จสมบูรณ์', + mandarin: '完成' }, loadMore: { english: 'load more', @@ -3783,7 +5703,11 @@ export const localizations = { brazilian_portuguese: 'carregar mais', tok_pisin: 'bringim moa', indonesian: 'muat lebih banyak', - nepali: 'थप लोड गर्नुहोस्' + nepali: 'थप लोड गर्नुहोस्', + hindi: 'और लोड करें', + burmese: 'ပိုမိုဖွင့်ပါ', + thai: 'โหลดเพิ่มเติม', + mandarin: '加载更多' }, loading: { english: 'loading', @@ -3791,7 +5715,11 @@ export const localizations = { brazilian_portuguese: 'carregando', tok_pisin: 'loadim', indonesian: 'memuat', - nepali: 'लोड गर्दै' + nepali: 'लोड गर्दै', + hindi: 'लोड हो रहा है', + burmese: 'ဖွင့်နေသည်', + thai: 'กำลังโหลด', + mandarin: '加载中' }, assetMadeInvisibleAllQuests: { english: 'The asset has been made invisible for all quests', @@ -3799,7 +5727,11 @@ export const localizations = { brazilian_portuguese: 'O asset foi feito invisível para todas as quests', tok_pisin: 'Asset i make invisible long all quest', indonesian: 'Asset dibuat tidak terlihat untuk semua quest', - nepali: 'एसेट सबै क्वेस्टहरूको लागि अदृश्य बनाइएको छ' + nepali: 'एसेट सबै क्वेस्टहरूको लागि अदृश्य बनाइएको छ', + hindi: 'एसेट सभी क्वेस्ट के लिए अदृश्य बना दिया गया है', + burmese: 'အရာဝတ္ထုကို စွမ်းဆောင်ရည်အားလုံးအတွက် မမြင်ရအောင် ပြုလုပ်ထားသည်', + thai: 'ทรัพย์สินถูกทำให้มองไม่เห็นสำหรับเควสต์ทั้งหมด', + mandarin: '该资产已对所有任务设为不可见' }, assetMadeVisibleAllQuests: { english: 'The asset has been made visible for all quests', @@ -3807,7 +5739,12 @@ export const localizations = { brazilian_portuguese: 'O asset foi feito visível para todas as quests', tok_pisin: 'Asset i make visible long all quest', indonesian: 'Asset dibuat terlihat untuk semua quest', - nepali: 'एसेट सबै क्वेस्टहरूको लागि दृश्य बनाइएको छ' + nepali: 'एसेट सबै क्वेस्टहरूको लागि दृश्य बनाइएको छ', + hindi: 'एसेट सभी क्वेस्ट के लिए दृश्यमान बना दिया गया है', + burmese: + 'အရာဝတ္ထုကို စွမ်းဆောင်ရည်အားလုံးအတွက် မြင်နိုင်အောင် ပြုလုပ်ထားသည်', + thai: 'ทรัพย์สินถูกทำให้มองเห็นได้สำหรับเควสต์ทั้งหมด', + mandarin: '该资产已对所有任务设为可见' }, assetMadeInactiveAllQuests: { english: 'The asset has been made inactive for all quests', @@ -3815,7 +5752,12 @@ export const localizations = { brazilian_portuguese: 'O asset foi feito inativo para todas as quests', tok_pisin: 'Asset i make inactive long all quest', indonesian: 'Asset dibuat tidak aktif untuk semua quest', - nepali: 'एसेट सबै क्वेस्टहरूको लागि निष्क्रिय बनाइएको छ' + nepali: 'एसेट सबै क्वेस्टहरूको लागि निष्क्रिय बनाइएको छ', + hindi: 'एसेट सभी क्वेस्ट के लिए निष्क्रिय बना दिया गया है', + burmese: + 'အရာဝတ္ထုကို စွမ်းဆောင်ရည်အားလုံးအတွက် မလှုပ်ရှားအောင် ပြုလုပ်ထားသည်', + thai: 'ทรัพย์สินถูกทำให้ไม่ใช้งานสำหรับเควสต์ทั้งหมด', + mandarin: '该资产已对所有任务设为非活动' }, assetMadeActiveAllQuests: { english: 'The asset has been made active for all quests', @@ -3823,7 +5765,12 @@ export const localizations = { brazilian_portuguese: 'O asset foi feito ativo para todas as quests', tok_pisin: 'Asset i make active long all quest', indonesian: 'Asset dibuat aktif untuk semua quest', - nepali: 'एसेट सबै क्वेस्टहरूको लागि सक्रिय बनाइएको छ' + nepali: 'एसेट सबै क्वेस्टहरूको लागि सक्रिय बनाइएको छ', + hindi: 'एसेट सभी क्वेस्ट के लिए सक्रिय बना दिया गया है', + burmese: + 'အရာဝတ္ထုကို စွမ်းဆောင်ရည်အားလုံးအတွက် လှုပ်ရှားအောင် ပြုလုပ်ထားသည်', + thai: 'ทรัพย์สินถูกทำให้ใช้งานได้สำหรับเควสต์ทั้งหมด', + mandarin: '该资产已对所有任务设为活动' }, failedToUpdateAssetSettings: { english: 'Failed to update asset settings', @@ -3831,7 +5778,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao atualizar os ajustes do asset', tok_pisin: 'I no inap update asset settings', indonesian: 'Gagal mengupdate pengaturan asset', - nepali: 'एसेट सेटिङहरू अपडेट गर्न असफल' + nepali: 'एसेट सेटिङहरू अपडेट गर्न असफल', + hindi: 'एसेट सेटिंग अपडेट करने में विफल', + burmese: 'အရာဝတ္ထု ဆက်တင်များကို အပ်ဒိတ်လုပ်ရန် မအောင်မြင်ပါ', + thai: 'ไม่สามารถอัปเดตการตั้งค่าทรัพย์สินได้', + mandarin: '更新资产设置失败' }, assetMadeInvisibleQuest: { english: 'The asset has been made invisible for this quest', @@ -3839,7 +5790,11 @@ export const localizations = { brazilian_portuguese: 'O asset foi feito invisível para esta quest', tok_pisin: 'Asset i make invisible long quest', indonesian: 'Asset dibuat tidak terlihat untuk quest ini', - nepali: 'यो क्वेस्टको लागि एसेट अदृश्य बनाइएको छ' + nepali: 'यो क्वेस्टको लागि एसेट अदृश्य बनाइएको छ', + hindi: 'इस क्वेस्ट के लिए एसेट अदृश्य बना दिया गया है', + burmese: 'ဤစွမ်းဆောင်ရည်အတွက် အရာဝတ္ထုကို မမြင်ရအောင် ပြုလုပ်ထားသည်', + thai: 'ทรัพย์สินถูกทำให้มองไม่เห็นสำหรับเควสต์นี้', + mandarin: '该资产已对此任务设为不可见' }, assetMadeVisibleQuest: { english: 'The asset has been made visible for this quest', @@ -3847,7 +5802,11 @@ export const localizations = { brazilian_portuguese: 'O asset foi feito visível para esta quest', tok_pisin: 'Asset i make visible long quest', indonesian: 'Asset dibuat terlihat untuk quest ini', - nepali: 'यो क्वेस्टको लागि एसेट दृश्य बनाइएको छ' + nepali: 'यो क्वेस्टको लागि एसेट दृश्य बनाइएको छ', + hindi: 'इस क्वेस्ट के लिए एसेट दृश्यमान बना दिया गया है', + burmese: 'ဤစွမ်းဆောင်ရည်အတွက် အရာဝတ္ထုကို မြင်နိုင်အောင် ပြုလုပ်ထားသည်', + thai: 'ทรัพย์สินถูกทำให้มองเห็นได้สำหรับเควสต์นี้', + mandarin: '该资产已对此任务设为可见' }, assetMadeInactiveQuest: { english: 'The asset has been made inactive for this quest', @@ -3855,7 +5814,11 @@ export const localizations = { brazilian_portuguese: 'O asset foi feito inativo para esta quest', tok_pisin: 'Asset i make inactive long quest', indonesian: 'Asset dibuat tidak aktif untuk quest ini', - nepali: 'यो क्वेस्टको लागि एसेट निष्क्रिय बनाइएको छ' + nepali: 'यो क्वेस्टको लागि एसेट निष्क्रिय बनाइएको छ', + hindi: 'इस क्वेस्ट के लिए एसेट निष्क्रिय बना दिया गया है', + burmese: 'ဤစွမ်းဆောင်ရည်အတွက် အရာဝတ္ထုကို မလှုပ်ရှားအောင် ပြုလုပ်ထားသည်', + thai: 'ทรัพย์สินถูกทำให้ไม่ใช้งานสำหรับเควสต์นี้', + mandarin: '该资产已对此任务设为非活动' }, assetMadeActiveQuest: { english: 'The asset has been made active for this quest', @@ -3863,7 +5826,11 @@ export const localizations = { brazilian_portuguese: 'O asset foi feito ativo para esta quest', tok_pisin: 'Asset i make active long quest', indonesian: 'Asset dibuat aktif untuk quest ini', - nepali: 'यो क्वेस्टको लागि एसेट सक्रिय बनाइएको छ' + nepali: 'यो क्वेस्टको लागि एसेट सक्रिय बनाइएको छ', + hindi: 'इस क्वेस्ट के लिए एसेट सक्रिय बना दिया गया है', + burmese: 'ဤစွမ်းဆောင်ရည်အတွက် အရာဝတ္ထုကို လှုပ်ရှားအောင် ပြုလုပ်ထားသည်', + thai: 'ทรัพย์สินถูกทำให้ใช้งานได้สำหรับเควสต์นี้', + mandarin: '该资产已对此任务设为活动' }, assetSettings: { english: 'Asset Settings', @@ -3871,7 +5838,11 @@ export const localizations = { brazilian_portuguese: 'Ajustes do Asset', tok_pisin: 'Asset Settings', indonesian: 'Pengaturan Asset', - nepali: 'एसेट सेटिङहरू' + nepali: 'एसेट सेटिङहरू', + hindi: 'एसेट सेटिंग', + burmese: 'အရာဝတ္ထု ဆက်တင်များ', + thai: 'การตั้งค่าทรัพย์สิน', + mandarin: '资产设置' }, assetSettingsLoadError: { english: 'Error loading asset settings.', @@ -3879,7 +5850,11 @@ export const localizations = { brazilian_portuguese: 'Erro ao carregar as configurações do asset.', tok_pisin: 'I no inap load asset settings', indonesian: 'Gagal memuat pengaturan asset.', - nepali: 'एसेट सेटिङहरू लोड गर्दा त्रुटि।' + nepali: 'एसेट सेटिङहरू लोड गर्दा त्रुटि।', + hindi: 'एसेट सेटिंग लोड करने में त्रुटि।', + burmese: 'အရာဝတ္ထု ဆက်တင်များကို ဖွင့်ရာတွင် အမှားအယွင်း။', + thai: 'เกิดข้อผิดพลาดในการโหลดการตั้งค่าทรัพย์สิน', + mandarin: '加载资产设置时出错。' }, general: { english: 'General', @@ -3887,7 +5862,11 @@ export const localizations = { brazilian_portuguese: 'Geral', tok_pisin: 'General', indonesian: 'Umum', - nepali: 'सामान्य' + nepali: 'सामान्य', + hindi: 'सामान्य', + burmese: 'အထွေထွေ', + thai: 'ทั่วไป', + mandarin: '常规' }, currentQuest: { english: 'Current Quest', @@ -3895,7 +5874,11 @@ export const localizations = { brazilian_portuguese: 'Quest Atual', tok_pisin: 'Current Quest', indonesian: 'Quest Saat Ini', - nepali: 'हालको क्वेस्ट' + nepali: 'हालको क्वेस्ट', + hindi: 'वर्तमान क्वेस्ट', + burmese: 'လက်ရှိ စွမ်းဆောင်ရည်', + thai: 'เควสต์ปัจจุบัน', + mandarin: '当前任务' }, visibility: { english: 'Visibility', @@ -3903,7 +5886,11 @@ export const localizations = { brazilian_portuguese: 'Visibilidade', tok_pisin: 'Visibility', indonesian: 'Visibilitas', - nepali: 'दृश्यता' + nepali: 'दृश्यता', + hindi: 'दृश्यता', + burmese: 'မြင်နိုင်မှု', + thai: 'การมองเห็น', + mandarin: '可见性' }, active: { english: 'Active', @@ -3911,7 +5898,11 @@ export const localizations = { brazilian_portuguese: 'Ativo', tok_pisin: 'Active', indonesian: 'Aktif', - nepali: 'सक्रिय' + nepali: 'सक्रिय', + hindi: 'सक्रिय', + burmese: 'လှုပ်ရှားနေသည်', + thai: 'ใช้งาน', + mandarin: '活动' }, visibilityDescription: { english: @@ -3924,7 +5915,13 @@ export const localizations = { indonesian: 'Asset terlihat secara default di semua quest, kecuali disembunyikan secara individual.', nepali: - 'एसेट पूर्वनिर्धारित रूपमा सबै क्वेस्टहरूमा देखिन्छ, व्यक्तिगत रूपमा लुकाइएको बाहेक।' + 'एसेट पूर्वनिर्धारित रूपमा सबै क्वेस्टहरूमा देखिन्छ, व्यक्तिगत रूपमा लुकाइएको बाहेक।', + hindi: + 'एसेट डिफ़ॉल्ट रूप से सभी क्वेस्ट में दृश्यमान है, जब तक कि व्यक्तिगत रूप से छुपाया न जाए।', + burmese: + 'အရာဝတ္ထုသည် ပုံမှန်အားဖြင့် စွမ်းဆောင်ရည်အားလုံးတွင် မြင်နိုင်သည်၊ တစ်ခုချင်းစီ ဖျောက်ထားမှသာ မဟုတ်ပါက။', + thai: 'ทรัพย์สินจะมองเห็นได้โดยค่าเริ่มต้นในเควสต์ทั้งหมด เว้นแต่จะซ่อนเป็นรายการ', + mandarin: '资产默认在所有任务中可见,除非单独隐藏。' }, activeDescription: { english: @@ -3938,7 +5935,13 @@ export const localizations = { indonesian: 'Asset aktif dan dapat digunakan di semua quest, kecuali dinonaktifkan secara individual.', nepali: - 'एसेट सक्रिय छ र सबै क्वेस्टहरूमा प्रयोग गर्न सकिन्छ, व्यक्तिगत रूपमा निष्क्रिय पारिएको बाहेक।' + 'एसेट सक्रिय छ र सबै क्वेस्टहरूमा प्रयोग गर्न सकिन्छ, व्यक्तिगत रूपमा निष्क्रिय पारिएको बाहेक।', + hindi: + 'एसेट सक्रिय है और सभी क्वेस्ट में उपयोग किया जा सकता है, जब तक कि व्यक्तिगत रूप से निष्क्रिय न किया जाए।', + burmese: + 'အရာဝတ္ထုသည် လှုပ်ရှားနေပြီး စွမ်းဆောင်ရည်အားလုံးတွင် အသုံးပြုနိုင်သည်၊ တစ်ခုချင်းစီ ပိတ်ထားမှသာ မဟုတ်ပါက။', + thai: 'ทรัพย์สินใช้งานได้และสามารถใช้ในเควสต์ทั้งหมดได้ เว้นแต่จะปิดการใช้งานเป็นรายการ', + mandarin: '资产处于活动状态,可在所有任务中使用,除非单独停用。' }, visibilityDescriptionQuest: { english: @@ -3951,7 +5954,13 @@ export const localizations = { indonesian: 'Asset terlihat secara default di quest ini, kecuali disembunyikan secara individual.', nepali: - 'यो क्वेस्टमा एसेट पूर्वनिर्धारित रूपमा देखिन्छ, व्यक्तिगत रूपमा लुकाइएको बाहेक।' + 'यो क्वेस्टमा एसेट पूर्वनिर्धारित रूपमा देखिन्छ, व्यक्तिगत रूपमा लुकाइएको बाहेक।', + hindi: + 'इस क्वेस्ट में एसेट डिफ़ॉल्ट रूप से दृश्यमान है, जब तक कि व्यक्तिगत रूप से छुपाया न जाए।', + burmese: + 'ဤစွမ်းဆောင်ရည်တွင် အရာဝတ္ထုသည် ပုံမှန်အားဖြင့် မြင်နိုင်သည်၊ တစ်ခုချင်းစီ ဖျောက်ထားမှသာ မဟုတ်ပါက။', + thai: 'ทรัพย์สินจะมองเห็นได้โดยค่าเริ่มต้นในเควสต์นี้ เว้นแต่จะซ่อนเป็นรายการ', + mandarin: '资产默认在此任务中可见,除非单独隐藏。' }, assetHiddenAllQuests: { english: @@ -3964,7 +5973,13 @@ export const localizations = { 'Asset i hait long olgeta quest na yu no ken mekim save long wanpela.', indonesian: 'Asset disembunyikan di semua quest dan tidak dapat dibuat terlihat di salah satunya.', - nepali: 'एसेट सबै क्वेस्टहरूमा लुकाइएको छ र कुनैमा पनि देखाउन सकिँदैन।' + nepali: 'एसेट सबै क्वेस्टहरूमा लुकाइएको छ र कुनैमा पनि देखाउन सकिँदैन।', + hindi: + 'एसेट सभी क्वेस्ट में छुपाया गया है और किसी में भी दृश्यमान नहीं बनाया जा सकता।', + burmese: + 'အရာဝတ္ထုသည် စွမ်းဆောင်ရည်အားလုံးတွင် ဖျောက်ထားပြီး မည်သည့်တစ်ခုတွင်မှ မြင်နိုင်အောင် မပြုလုပ်နိုင်ပါ။', + thai: 'ทรัพย์สินถูกซ่อนในเควสต์ทั้งหมดและไม่สามารถทำให้มองเห็นได้ในเควสต์ใดๆ', + mandarin: '资产在所有任务中隐藏,无法在任何任务中设为可见。' }, assetDisabledAllQuests: { english: @@ -3977,7 +5992,12 @@ export const localizations = { 'Asset i stop long olgeta quest na yu no ken usim long wanpela hap.', indonesian: 'Asset dinonaktifkan di semua quest dan tidak dapat digunakan di mana pun.', - nepali: 'एसेट सबै क्वेस्टहरूमा असक्षम छ र कहीँ पनि प्रयोग गर्न सकिँदैन।' + nepali: 'एसेट सबै क्वेस्टहरूमा असक्षम छ र कहीँ पनि प्रयोग गर्न सकिँदैन।', + hindi: 'एसेट सभी क्वेस्ट में अक्षम है और कहीं भी उपयोग नहीं किया जा सकता।', + burmese: + 'အရာဝတ္ထုသည် စွမ်းဆောင်ရည်အားလုံးတွင် ပိတ်ထားပြီး မည်သည့်နေရာတွင်မှ အသုံးပြုနိုင်မည်မဟုတ်ပါ။', + thai: 'ทรัพย์สินถูกปิดการใช้งานในเควสต์ทั้งหมดและไม่สามารถใช้งานได้ทุกที่', + mandarin: '资产在所有任务中已禁用,无法在任何地方使用。' }, assetGeneralSettingsDescription: { english: 'These settings affect how the asset behaves across all quests.', @@ -3989,7 +6009,12 @@ export const localizations = { 'Ol dispela setting i senisim how asset i wok long olgeta quest.', indonesian: 'Pengaturan ini mempengaruhi bagaimana asset berperilaku di semua quest.', - nepali: 'यी सेटिङहरूले सबै क्वेस्टहरूमा एसेटको व्यवहारलाई प्रभाव पार्छन्।' + nepali: 'यी सेटिङहरूले सबै क्वेस्टहरूमा एसेटको व्यवहारलाई प्रभाव पार्छन्।', + hindi: 'ये सेटिंग सभी क्वेस्ट में एसेट के व्यवहार को प्रभावित करती हैं।', + burmese: + 'ဤဆက်တင်များသည် စွမ်းဆောင်ရည်အားလုံးတွင် အရာဝတ္ထု၏ လုပ်ဆောင်ပုံကို သက်ရောက်မှုရှိသည်။', + thai: 'การตั้งค่าเหล่านี้ส่งผลต่อพฤติกรรมของทรัพย์สินในเควสต์ทั้งหมด', + mandarin: '这些设置会影响资产在所有任务中的行为。' }, questSpecificSettingsDescription: { english: @@ -4002,7 +6027,14 @@ export const localizations = { 'Ol dispela setting i senisim how asset i wok long dispela quest.', indonesian: 'Pengaturan ini mempengaruhi bagaimana asset berperilaku di quest spesifik ini.', - nepali: 'यी सेटिङहरूले यो विशेष क्वेस्टमा एसेटको व्यवहारलाई प्रभाव पार्छन्।' + nepali: + 'यी सेटिङहरूले यो विशेष क्वेस्टमा एसेटको व्यवहारलाई प्रभाव पार्छन्।', + hindi: + 'ये सेटिंग इस विशिष्ट क्वेस्ट में एसेट के व्यवहार को प्रभावित करती हैं।', + burmese: + 'ဤဆက်တင်များသည် ဤအထူးစွမ်းဆောင်ရည်တွင် အရာဝတ္ထု၏ လုပ်ဆောင်ပုံကို သက်ရောက်မှုရှိသည်။', + thai: 'การตั้งค่าเหล่านี้ส่งผลต่อพฤติกรรมของทรัพย์สินในเควสต์เฉพาะนี้', + mandarin: '这些设置会影响资产在此特定任务中的行为。' }, assetDisabledWarning: { english: @@ -4016,7 +6048,13 @@ export const localizations = { indonesian: 'Asset ini dinonaktifkan secara global. Anda tidak dapat mengubah pengaturannya untuk quest ini.', nepali: - 'यो एसेट विश्वव्यापी रूपमा असक्षम छ। तपाईं यो क्वेस्टको लागि यसको सेटिङहरू परिवर्तन गर्न सक्नुहुन्न।' + 'यो एसेट विश्वव्यापी रूपमा असक्षम छ। तपाईं यो क्वेस्टको लागि यसको सेटिङहरू परिवर्तन गर्न सक्नुहुन्न।', + hindi: + 'यह एसेट वैश्विक रूप से अक्षम है। आप इस क्वेस्ट के लिए इसकी सेटिंग बदल नहीं सकते।', + burmese: + 'ဤအရာဝတ္ထုသည် ကမ္ဘာတစ်ဝှမ်းတွင် ပိတ်ထားသည်။ ဤစွမ်းဆောင်ရည်အတွက် ၎င်း၏ဆက်တင်များကို သင်ပြောင်းလဲ၍မရပါ။', + thai: 'ทรัพย์สินนี้ถูกปิดการใช้งานทั่วโลก คุณไม่สามารถเปลี่ยนการตั้งค่าสำหรับเควสต์นี้ได้', + mandarin: '此资产已在全局禁用。您无法更改此任务的设置。' }, assetVisibleThisQuest: { english: 'The asset is shown in this quest. Unless hidden globally.', @@ -4028,7 +6066,13 @@ export const localizations = { 'Asset i save long dispela quest. Sapos i no hait long olgeta hap.', indonesian: 'Asset ditampilkan di quest ini. Kecuali disembunyikan secara global.', - nepali: 'यो क्वेस्टमा एसेट देखाइएको छ। विश्वव्यापी रूपमा लुकाइएको बाहेक।' + nepali: 'यो क्वेस्टमा एसेट देखाइएको छ। विश्वव्यापी रूपमा लुकाइएको बाहेक।', + hindi: + 'इस क्वेस्ट में एसेट दिखाया गया है। जब तक कि वैश्विक रूप से छुपाया न गया हो।', + burmese: + 'ဤစွမ်းဆောင်ရည်တွင် အရာဝတ္ထုကို ပြသထားသည်။ ကမ္ဘာတစ်ဝှမ်းတွင် ဖျောက်ထားမှသာ မဟုတ်ပါက။', + thai: 'ทรัพย์สินจะแสดงในเควสต์นี้ เว้นแต่จะซ่อนไว้ทั่วโลก', + mandarin: '资产在此任务中显示。除非在全局隐藏。' }, assetHiddenThisQuest: { english: 'The asset is hidden in this quest.', @@ -4036,7 +6080,11 @@ export const localizations = { brazilian_portuguese: 'O asset está oculto nesta quest.', tok_pisin: 'Asset i hait long dispela quest.', indonesian: 'Asset disembunyikan di quest ini.', - nepali: 'यो क्वेस्टमा एसेट लुकाइएको छ।' + nepali: 'यो क्वेस्टमा एसेट लुकाइएको छ।', + hindi: 'इस क्वेस्ट में एसेट छुपाया गया है।', + burmese: 'ဤစွမ်းဆောင်ရည်တွင် အရာဝတ္ထုကို ဖျောက်ထားသည်။', + thai: 'ทรัพย์สินถูกซ่อนในเควสต์นี้', + mandarin: '资产在此任务中隐藏。' }, assetActiveThisQuest: { english: @@ -4050,7 +6098,13 @@ export const localizations = { indonesian: 'Asset dapat digunakan di quest ini. Kecuali dinonaktifkan secara global.', nepali: - 'यो क्वेस्टमा एसेट प्रयोग गर्न सकिन्छ। विश्वव्यापी रूपमा निष्क्रिय पारिएको बाहेक।' + 'यो क्वेस्टमा एसेट प्रयोग गर्न सकिन्छ। विश्वव्यापी रूपमा निष्क्रिय पारिएको बाहेक।', + hindi: + 'इस क्वेस्ट में एसेट का उपयोग किया जा सकता है। जब तक कि वैश्विक रूप से निष्क्रिय न किया गया हो।', + burmese: + 'ဤစွမ်းဆောင်ရည်တွင် အရာဝတ္ထုကို အသုံးပြုနိုင်သည်။ ကမ္ဘာတစ်ဝှမ်းတွင် ပိတ်ထားမှသာ မဟုတ်ပါက။', + thai: 'ทรัพย์สินสามารถใช้ในเควสต์นี้ได้ เว้นแต่จะปิดการใช้งานทั่วโลก', + mandarin: '资产可在此任务中使用。除非在全局停用。' }, assetInactiveThisQuest: { english: 'The asset is not available in this quest.', @@ -4058,7 +6112,11 @@ export const localizations = { brazilian_portuguese: 'O asset não está disponível nesta quest.', tok_pisin: 'Asset i no stap long dispela quest.', indonesian: 'Asset tidak tersedia di quest ini.', - nepali: 'यो क्वेस्टमा एसेट उपलब्ध छैन।' + nepali: 'यो क्वेस्टमा एसेट उपलब्ध छैन।', + hindi: 'इस क्वेस्ट में एसेट उपलब्ध नहीं है।', + burmese: 'ဤစွမ်းဆောင်ရည်တွင် အရာဝတ္ထု မရရှိနိုင်ပါ။', + thai: 'ทรัพย์สินไม่พร้อมใช้งานในเควสต์นี้', + mandarin: '资产在此任务中不可用。' }, downloadProjectConfirmation: { english: 'Download this project for offline use?', @@ -4066,7 +6124,12 @@ export const localizations = { brazilian_portuguese: 'Baixar este projeto para uso offline?', tok_pisin: 'Daunim dispela project long usim taim i no gat internet?', indonesian: 'Unduh proyek ini untuk penggunaan offline?', - nepali: 'अफलाइन प्रयोगको लागि यो प्रोजेक्ट डाउनलोड गर्ने?' + nepali: 'अफलाइन प्रयोगको लागि यो प्रोजेक्ट डाउनलोड गर्ने?', + hindi: 'ऑफलाइन उपयोग के लिए इस परियोजना को डाउनलोड करें?', + burmese: + 'အင်တာနက်မရှိသော အချိန်တွင် အသုံးပြုရန် ဤစီမံကိန်းကို ဒေါင်းလုဒ်လုပ်မည်လား?', + thai: 'ดาวน์โหลดโครงการนี้เพื่อใช้งานแบบออฟไลน์?', + mandarin: '下载此项目以供离线使用?' }, downloadQuestConfirmation: { english: 'Download this quest for offline use?', @@ -4074,7 +6137,12 @@ export const localizations = { brazilian_portuguese: 'Baixar esta quest para uso offline?', tok_pisin: 'Daunim dispela quest long usim taim i no gat internet?', indonesian: 'Unduh quest ini untuk penggunaan offline?', - nepali: 'अफलाइन प्रयोगको लागि यो क्वेस्ट डाउनलोड गर्ने?' + nepali: 'अफलाइन प्रयोगको लागि यो क्वेस्ट डाउनलोड गर्ने?', + hindi: 'ऑफलाइन उपयोग के लिए इस क्वेस्ट को डाउनलोड करें?', + burmese: + 'အင်တာနက်မရှိသော အချိန်တွင် အသုံးပြုရန် ဤစွမ်းဆောင်ရည်ကို ဒေါင်းလုဒ်လုပ်မည်လား?', + thai: 'ดาวน์โหลดเควสต์นี้เพื่อใช้งานแบบออฟไลน์?', + mandarin: '下载此任务以供离线使用?' }, thisWillDownload: { english: 'This will download:', @@ -4082,7 +6150,11 @@ export const localizations = { brazilian_portuguese: 'Isso baixará:', tok_pisin: 'Dispela bai daunim:', indonesian: 'Ini akan mengunduh:', - nepali: 'यसले डाउनलोड गर्नेछ:' + nepali: 'यसले डाउनलोड गर्नेछ:', + hindi: 'यह डाउनलोड करेगा:', + burmese: 'ဤအရာသည် ဒေါင်းလုဒ်လုပ်မည်:', + thai: 'สิ่งนี้จะดาวน์โหลด:', + mandarin: '这将下载:' }, translations: { english: 'Translations', @@ -4090,7 +6162,11 @@ export const localizations = { brazilian_portuguese: 'Traduções', tok_pisin: 'Ol Translation', indonesian: 'Terjemahan', - nepali: 'अनुवादहरू' + nepali: 'अनुवादहरू', + hindi: 'अनुवाद', + burmese: 'ဘာသာပြန်များ', + thai: 'การแปล', + mandarin: '翻译' }, doRecord: { english: 'Record', @@ -4098,7 +6174,11 @@ export const localizations = { brazilian_portuguese: 'Gravar', tok_pisin: 'Rekodem', indonesian: 'Rekam', - nepali: 'रेकर्ड गर्नुहोस्' + nepali: 'रेकर्ड गर्नुहोस्', + hindi: 'रिकॉर्ड करें', + burmese: 'မှတ်တမ်းတင်ပါ', + thai: 'บันทึก', + mandarin: '录制' }, isRecording: { english: 'Recording...', @@ -4106,7 +6186,11 @@ export const localizations = { brazilian_portuguese: 'Gravando...', tok_pisin: 'Recording...', indonesian: 'Merekam...', - nepali: 'रेकर्ड गर्दै...' + nepali: 'रेकर्ड गर्दै...', + hindi: 'रिकॉर्डिंग...', + burmese: 'မှတ်တမ်းတင်နေသည်...', + thai: 'กำลังบันทึก...', + mandarin: '录制中...' }, recordTo: { english: 'Record to', @@ -4114,7 +6198,11 @@ export const localizations = { brazilian_portuguese: 'Gravar em', tok_pisin: 'Rekodem long', indonesian: 'Rekam ke', - nepali: 'रेकर्ड गर्नुहोस्' + nepali: 'रेकर्ड गर्नुहोस्', + hindi: 'रिकॉर्ड करें', + burmese: 'မှတ်တမ်းတင်ရန်', + thai: 'บันทึกไปยัง', + mandarin: '录制到' }, after: { english: 'After', @@ -4122,7 +6210,11 @@ export const localizations = { brazilian_portuguese: 'Depois de', tok_pisin: 'Despela', indonesian: 'Setelah', - nepali: 'बादमा' + nepali: 'बादमा', + hindi: 'बाद', + burmese: 'ပြီးနောက်', + thai: 'หลังจาก', + mandarin: '之后' }, noLabelSelected: { english: 'No label selected', @@ -4130,7 +6222,11 @@ export const localizations = { brazilian_portuguese: 'Nenhum rótulo selecionado', tok_pisin: 'No label i stap', indonesian: 'Tidak ada label dipilih', - nepali: 'कुनै लेबल चयन गरिएको छैन' + nepali: 'कुनै लेबल चयन गरिएको छैन', + hindi: 'कोई लेबल चयनित नहीं', + burmese: 'အညွှန်းတံဆိပ် ရွေးချယ်ထားခြင်း မရှိပါ', + thai: 'ไม่ได้เลือกป้ายกำกับ', + mandarin: '未选择标签' }, startRecordingSession: { english: 'Start Recording Session', @@ -4138,7 +6234,11 @@ export const localizations = { brazilian_portuguese: 'Iniciar Sessão de Gravação', tok_pisin: 'Stat Rekodem Taim', indonesian: 'Mulai Sesi Rekaman', - nepali: 'रेकर्डिङ सत्र सुरु गर्नुहोस्' + nepali: 'रेकर्डिङ सत्र सुरु गर्नुहोस्', + hindi: 'रिकॉर्डिंग सत्र शुरू करें', + burmese: 'မှတ်တမ်းတင်ခြင်း အစည်းအဝေးကို စတင်ပါ', + thai: 'เริ่มเซสชันการบันทึก', + mandarin: '开始录制会话' }, typeToConfirm: { english: 'Type {text} to confirm', @@ -4146,7 +6246,11 @@ export const localizations = { brazilian_portuguese: 'Digite {text} para confirmar', tok_pisin: 'Raitim {text} bilong siaim', indonesian: 'Ketik {text} untuk mengkonfirmasi', - nepali: 'पुष्टि गर्न {text} टाइप गर्नुहोस्' + nepali: 'पुष्टि गर्न {text} टाइप गर्नुहोस्', + hindi: 'पुष्टि करने के लिए {text} टाइप करें', + burmese: 'အတည်ပြုရန် {text} ကို ရိုက်ထည့်ပါ', + thai: 'พิมพ์ {text} เพื่อยืนยัน', + mandarin: '输入 {text} 以确认' }, confirmDeletion: { english: 'Confirm Deletion', @@ -4154,7 +6258,11 @@ export const localizations = { brazilian_portuguese: 'Confirmar Exclusão', tok_pisin: 'Siaim Rausim', indonesian: 'Konfirmasi Penghapusan', - nepali: 'मेटाउने पुष्टि गर्नुहोस्' + nepali: 'मेटाउने पुष्टि गर्नुहोस्', + hindi: 'हटाने की पुष्टि करें', + burmese: 'ဖျက်ခြင်းကို အတည်ပြုပါ', + thai: 'ยืนยันการลบ', + mandarin: '确认删除' }, deleting: { english: 'Deleting...', @@ -4162,7 +6270,11 @@ export const localizations = { brazilian_portuguese: 'Excluindo...', tok_pisin: 'Rausim nau...', indonesian: 'Menghapus...', - nepali: 'मेटाउँदै...' + nepali: 'मेटाउँदै...', + hindi: 'हटा रहे हैं...', + burmese: 'ဖျက်နေသည်...', + thai: 'กำลังลบ...', + mandarin: '正在删除...' }, audioSegments: { english: 'Audio Segments', @@ -4170,7 +6282,11 @@ export const localizations = { brazilian_portuguese: 'Pistas de Áudio', tok_pisin: 'Ol audio track', indonesian: 'Trek audio', - nepali: 'अडियो खण्डहरू' + nepali: 'अडियो खण्डहरू', + hindi: 'ऑडियो सेगमेंट', + burmese: 'အသံအပိုင်းများ', + thai: 'ส่วนเสียง', + mandarin: '音频片段' }, audioSegment: { english: 'Audio Segment', @@ -4178,7 +6294,11 @@ export const localizations = { brazilian_portuguese: 'Pista de Áudio', tok_pisin: 'Ol audio track', indonesian: 'Trek audio', - nepali: 'अडियो खण्ड' + nepali: 'अडियो खण्ड', + hindi: 'ऑडियो सेगमेंट', + burmese: 'အသံအပိုင်း', + thai: 'ส่วนเสียง', + mandarin: '音频片段' }, asAssets: { english: 'as Assets', @@ -4186,7 +6306,11 @@ export const localizations = { brazilian_portuguese: 'como Assets', tok_pisin: 'as Assets', indonesian: 'sebagai Assets', - nepali: 'एसेटहरूको रूपमा' + nepali: 'एसेटहरूको रूपमा', + hindi: 'एसेट के रूप में', + burmese: 'အရာဝတ္ထုများအဖြစ်', + thai: 'เป็นทรัพย์สิน', + mandarin: '作为资产' }, asAsset: { english: 'as Asset', @@ -4194,7 +6318,11 @@ export const localizations = { brazilian_portuguese: 'como Asset', tok_pisin: 'as Asset', indonesian: 'sebagai Asset', - nepali: 'एसेटको रूपमा' + nepali: 'एसेटको रूपमा', + hindi: 'एसेट के रूप में', + burmese: 'အရာဝတ္ထုအဖြစ်', + thai: 'เป็นทรัพย์สิน', + mandarin: '作为资产' }, save: { english: 'Save', @@ -4202,7 +6330,11 @@ export const localizations = { brazilian_portuguese: 'Salvar', tok_pisin: 'Save', indonesian: 'Simpan', - nepali: 'सुरक्षित गर्नुहोस्' + nepali: 'सुरक्षित गर्नुहोस्', + hindi: 'सहेजें', + burmese: 'သိမ်းဆည်းပါ', + thai: 'บันทึก', + mandarin: '保存' }, projectDirectory: { english: 'Project Directory', @@ -4210,7 +6342,11 @@ export const localizations = { brazilian_portuguese: 'Diretório de Projeto', tok_pisin: 'Project Directory', indonesian: 'Direktori Proyek', - nepali: 'प्रोजेक्ट डाइरेक्टरी' + nepali: 'प्रोजेक्ट डाइरेक्टरी', + hindi: 'परियोजना निर्देशिका', + burmese: 'စီမံကိန်း ဖိုင်တွဲ', + thai: 'ไดเรกทอรีโครงการ', + mandarin: '项目目录' }, projectMadePublic: { english: 'The project has been made public', @@ -4218,7 +6354,11 @@ export const localizations = { brazilian_portuguese: 'O projeto foi tornado público', tok_pisin: 'Project i mekim public nau', indonesian: 'Proyek telah dibuat publik', - nepali: 'प्रोजेक्ट सार्वजनिक बनाइयो' + nepali: 'प्रोजेक्ट सार्वजनिक बनाइयो', + hindi: 'परियोजना सार्वजनिक बना दी गई है', + burmese: 'စီမံကိန်းကို အများပြည်သူသို့ ပြုလုပ်ထားသည်', + thai: 'โครงการถูกทำให้เป็นสาธารณะแล้ว', + mandarin: '项目已设为公开' }, projectMadePrivate: { english: 'The project has been made private', @@ -4226,7 +6366,11 @@ export const localizations = { brazilian_portuguese: 'O projeto foi tornado privado', tok_pisin: 'Project i mekim private nau', indonesian: 'Proyek telah dibuat pribadi', - nepali: 'प्रोजेक्ट निजी बनाइयो' + nepali: 'प्रोजेक्ट निजी बनाइयो', + hindi: 'परियोजना निजी बना दी गई है', + burmese: 'စီမံကိန်းကို ကိုယ်ပိုင် ပြုလုပ်ထားသည်', + thai: 'โครงการถูกทำให้เป็นส่วนตัวแล้ว', + mandarin: '项目已设为私有' }, projectMadeInvisible: { english: 'The project has been made invisible', @@ -4234,7 +6378,11 @@ export const localizations = { brazilian_portuguese: 'O projeto foi tornado invisível', tok_pisin: 'Project i mekim hait nau', indonesian: 'Proyek telah dibuat tidak terlihat', - nepali: 'प्रोजेक्ट अदृश्य बनाइएको छ' + nepali: 'प्रोजेक्ट अदृश्य बनाइएको छ', + hindi: 'परियोजना अदृश्य बना दी गई है', + burmese: 'စီမံကိန်းကို မမြင်ရအောင် ပြုလုပ်ထားသည်', + thai: 'โครงการถูกทำให้มองไม่เห็นแล้ว', + mandarin: '项目已设为不可见' }, projectMadeVisible: { english: 'The project has been made visible', @@ -4242,7 +6390,11 @@ export const localizations = { brazilian_portuguese: 'O projeto foi tornado visível', tok_pisin: 'Project i mekim save nau', indonesian: 'Proyek telah dibuat terlihat', - nepali: 'प्रोजेक्ट दृश्य बनाइएको छ' + nepali: 'प्रोजेक्ट दृश्य बनाइएको छ', + hindi: 'परियोजना दृश्यमान बना दी गई है', + burmese: 'စီမံကိန်းကို မြင်နိုင်အောင် ပြုလုပ်ထားသည်', + thai: 'โครงการถูกทำให้มองเห็นได้แล้ว', + mandarin: '项目已设为可见' }, projectMadeInactive: { english: 'The project has been made inactive', @@ -4250,7 +6402,11 @@ export const localizations = { brazilian_portuguese: 'O projeto foi tornado inativo', tok_pisin: 'Project i mekim stop nau', indonesian: 'Proyek telah dibuat tidak aktif', - nepali: 'प्रोजेक्ट निष्क्रिय बनाइएको छ' + nepali: 'प्रोजेक्ट निष्क्रिय बनाइएको छ', + hindi: 'परियोजना निष्क्रिय बना दी गई है', + burmese: 'စီမံကိန်းကို မလှုပ်ရှားအောင် ပြုလုပ်ထားသည်', + thai: 'โครงการถูกทำให้ไม่ใช้งานแล้ว', + mandarin: '项目已设为非活动' }, projectMadeActive: { english: 'The project has been made active', @@ -4258,7 +6414,11 @@ export const localizations = { brazilian_portuguese: 'O projeto foi tornado ativo', tok_pisin: 'Project i mekim active nau', indonesian: 'Proyek telah dibuat aktif', - nepali: 'प्रोजेक्ट सक्रिय बनाइएको छ' + nepali: 'प्रोजेक्ट सक्रिय बनाइएको छ', + hindi: 'परियोजना सक्रिय बना दी गई है', + burmese: 'စီမံကိန်းကို လှုပ်ရှားအောင် ပြုလုပ်ထားသည်', + thai: 'โครงการถูกทำให้ใช้งานได้แล้ว', + mandarin: '项目已设为活动' }, failedToUpdateProjectSettings: { english: 'Failed to update project settings', @@ -4266,7 +6426,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao atualizar as configurações do projeto', tok_pisin: 'I no inap update project settings', indonesian: 'Gagal mengupdate pengaturan proyek', - nepali: 'प्रोजेक्ट सेटिङहरू अपडेट गर्न असफल' + nepali: 'प्रोजेक्ट सेटिङहरू अपडेट गर्न असफल', + hindi: 'परियोजना सेटिंग अपडेट करने में विफल', + burmese: 'စီမံကိန်း ဆက်တင်များကို အပ်ဒိတ်လုပ်ရန် မအောင်မြင်ပါ', + thai: 'ไม่สามารถอัปเดตการตั้งค่าโครงการได้', + mandarin: '更新项目设置失败' }, failedToUpdateProjectVisibility: { english: 'Failed to update project visibility', @@ -4274,7 +6438,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao atualizar a visibilidade do projeto', tok_pisin: 'I no inap update project visibility', indonesian: 'Gagal mengupdate visibilitas proyek', - nepali: 'प्रोजेक्ट दृश्यता अपडेट गर्न असफल' + nepali: 'प्रोजेक्ट दृश्यता अपडेट गर्न असफल', + hindi: 'परियोजना दृश्यता अपडेट करने में विफल', + burmese: 'စီမံကိန်း မြင်နိုင်မှုကို အပ်ဒိတ်လုပ်ရန် မအောင်မြင်ပါ', + thai: 'ไม่สามารถอัปเดตการมองเห็นของโครงการได้', + mandarin: '更新项目可见性失败' }, failedToUpdateProjectActiveStatus: { english: 'Failed to update project active status', @@ -4282,7 +6450,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao atualizar o status ativo do projeto', tok_pisin: 'I no inap update project active status', indonesian: 'Gagal mengupdate status aktif proyek', - nepali: 'प्रोजेक्ट सक्रिय स्थिति अपडेट गर्न असफल' + nepali: 'प्रोजेक्ट सक्रिय स्थिति अपडेट गर्न असफल', + hindi: 'परियोजना सक्रिय स्थिति अपडेट करने में विफल', + burmese: 'စီမံကိန်း လှုပ်ရှားနေသော အခြေအနေကို အပ်ဒိတ်လုပ်ရန် မအောင်မြင်ပါ', + thai: 'ไม่สามารถอัปเดตสถานะการใช้งานของโครงการได้', + mandarin: '更新项目活动状态失败' }, projectSettingsLoadError: { english: 'Error loading quest settings.', @@ -4290,7 +6462,11 @@ export const localizations = { brazilian_portuguese: 'Erro ao carregar as configurações da quest.', tok_pisin: 'I no inap load quest settings.', indonesian: 'Gagal memuat pengaturan quest.', - nepali: 'क्वेस्ट सेटिङहरू लोड गर्दा त्रुटि।' + nepali: 'क्वेस्ट सेटिङहरू लोड गर्दा त्रुटि।', + hindi: 'क्वेस्ट सेटिंग लोड करने में त्रुटि।', + burmese: 'စွမ်းဆောင်ရည် ဆက်တင်များကို ဖွင့်ရာတွင် အမှားအယွင်း။', + thai: 'เกิดข้อผิดพลาดในการโหลดการตั้งค่าเควสต์', + mandarin: '加载任务设置时出错。' }, projectSettings: { english: 'Project Settings', @@ -4298,7 +6474,11 @@ export const localizations = { brazilian_portuguese: 'Configurações do Projeto', tok_pisin: 'Project Settings', indonesian: 'Pengaturan Proyek', - nepali: 'प्रोजेक्ट सेटिङहरू' + nepali: 'प्रोजेक्ट सेटिङहरू', + hindi: 'परियोजना सेटिंग', + burmese: 'စီမံကိန်း ဆက်တင်များ', + thai: 'การตั้งค่าโครงการ', + mandarin: '项目设置' }, publicProjectDescription: { english: 'Anyone can view and contribute to this project', @@ -4307,7 +6487,11 @@ export const localizations = { 'Qualquer pessoa pode ver e contribuir para este projeto', tok_pisin: 'Olgeta man i ken lukim na contributim long dispela project', indonesian: 'Siapa saja dapat melihat dan berkontribusi pada proyek ini', - nepali: 'जोसुकैले यो प्रोजेक्ट हेर्न र योगदान गर्न सक्छन्' + nepali: 'जोसुकैले यो प्रोजेक्ट हेर्न र योगदान गर्न सक्छन्', + hindi: 'कोई भी इस परियोजना को देख और योगदान कर सकता है', + burmese: 'မည်သူမဆို ဤစီမံကိန်းကို ကြည့်ရှုနိုင်ပြီး ပါဝင်ဆောင်ရွက်နိုင်သည်', + thai: 'ทุกคนสามารถดูและมีส่วนร่วมในโครงการนี้ได้', + mandarin: '任何人都可以查看和贡献此项目' }, visibleProjectDescription: { english: @@ -4321,7 +6505,13 @@ export const localizations = { indonesian: 'Proyek ini muncul di daftar publik dan dapat ditemukan oleh semua pengguna.', nepali: - 'यो प्रोजेक्ट सार्वजनिक सूचीहरूमा देखिन्छ र सबै प्रयोगकर्ताहरूले फेला पार्न सक्छन्।' + 'यो प्रोजेक्ट सार्वजनिक सूचीहरूमा देखिन्छ र सबै प्रयोगकर्ताहरूले फेला पार्न सक्छन्।', + hindi: + 'यह परियोजना सार्वजनिक सूचियों में दिखाई देती है और सभी उपयोगकर्ताओं द्वारा खोजी जा सकती है।', + burmese: + 'ဤစီမံကိန်းသည် အများပြည်သူ စာရင်းများတွင် ပေါ်လာပြီး အားလုံးသော အသုံးပြုသူများက ရှာဖွေနိုင်သည်။', + thai: 'โครงการนี้จะปรากฏในรายการสาธารณะและสามารถค้นพบได้โดยผู้ใช้ทุกคน', + mandarin: '此项目会出现在公共列表中,所有用户都可以发现它。' }, invisibleProjectDescription: { english: @@ -4334,7 +6524,13 @@ export const localizations = { 'Dispela project i no save long project directory olsem search result.', indonesian: 'Proyek ini tidak ditampilkan di direktori proyek atau hasil pencarian.', - nepali: 'यो प्रोजेक्ट प्रोजेक्ट डाइरेक्टरीहरू वा खोज परिणामहरूमा देखिँदैन।' + nepali: 'यो प्रोजेक्ट प्रोजेक्ट डाइरेक्टरीहरू वा खोज परिणामहरूमा देखिँदैन।', + hindi: + 'यह परियोजना परियोजना निर्देशिकाओं या खोज परिणामों में प्रदर्शित नहीं होती है।', + burmese: + 'ဤစီမံကိန်းသည် စီမံကိန်း ဖိုင်တွဲများ သို့မဟုတ် ရှာဖွေမှု ရလဒ်များတွင် မပြသပါ။', + thai: 'โครงการนี้จะไม่แสดงในไดเรกทอรีโครงการหรือผลการค้นหา', + mandarin: '此项目不会显示在项目目录或搜索结果中。' }, activeProjectDescription: { english: 'The project is currently open for viewing and contributions.', @@ -4344,7 +6540,12 @@ export const localizations = { 'O projeto está atualmente aberto para visualização e contribuições.', tok_pisin: 'Dispela project i open nau long lukim na contributim.', indonesian: 'Proyek saat ini terbuka untuk dilihat dan kontribusi.', - nepali: 'प्रोजेक्ट हाल हेर्न र योगदानका लागि खुला छ।' + nepali: 'प्रोजेक्ट हाल हेर्न र योगदानका लागि खुला छ।', + hindi: 'परियोजना वर्तमान में देखने और योगदान के लिए खुली है।', + burmese: + 'စီမံကိန်းသည် လက်ရှိတွင် ကြည့်ရှုခြင်းနှင့် ပါဝင်ဆောင်ရွက်ခြင်းအတွက် ဖွင့်ထားသည်။', + thai: 'โครงการเปิดให้ดูและมีส่วนร่วมในขณะนี้', + mandarin: '项目目前开放查看和贡献。' }, inactiveProjectDescription: { english: @@ -4356,7 +6557,13 @@ export const localizations = { tok_pisin: 'Dispela project i no wok nau na i no acceptim contributim.', indonesian: 'Proyek ini saat ini tidak aktif dan tidak menerima kontribusi.', - nepali: 'यो प्रोजेक्ट हाल निष्क्रिय छ र योगदानहरू स्वीकार गर्दैन।' + nepali: 'यो प्रोजेक्ट हाल निष्क्रिय छ र योगदानहरू स्वीकार गर्दैन।', + hindi: + 'यह परियोजना वर्तमान में निष्क्रिय है और योगदान स्वीकार नहीं कर रही है।', + burmese: + 'ဤစီမံကိန်းသည် လက်ရှိတွင် မလှုပ်ရှားနေပြီး ပါဝင်ဆောင်ရွက်မှုများကို လက်မခံပါ။', + thai: 'โครงการนี้ไม่ใช้งานในขณะนี้และไม่รับการมีส่วนร่วม', + mandarin: '此项目目前处于非活动状态,不接受贡献。' }, loadingOptions: { english: 'Loading options...', @@ -4364,7 +6571,11 @@ export const localizations = { brazilian_portuguese: 'Carregando opções...', tok_pisin: 'I loadim ol option...', indonesian: 'Memuat opsi...', - nepali: 'विकल्पहरू लोड गर्दै...' + nepali: 'विकल्पहरू लोड गर्दै...', + hindi: 'विकल्प लोड हो रहे हैं...', + burmese: 'ရွေးချယ်စရာများကို ဖွင့်နေသည်...', + thai: 'กำลังโหลดตัวเลือก...', + mandarin: '正在加载选项...' }, loadingTagCategories: { english: 'Loading tag categories...', @@ -4372,7 +6583,11 @@ export const localizations = { brazilian_portuguese: 'Carregando categorias de etiquetas...', tok_pisin: 'I loadim ol tag category...', indonesian: 'Memuat kategori tag...', - nepali: 'ट्याग श्रेणीहरू लोड गर्दै...' + nepali: 'ट्याग श्रेणीहरू लोड गर्दै...', + hindi: 'टैग श्रेणियां लोड हो रही हैं...', + burmese: 'အညွှန်းတံဆိပ် အမျိုးအစားများကို ဖွင့်နေသည်...', + thai: 'กำลังโหลดหมวดหมู่แท็ก...', + mandarin: '正在加载标签类别...' }, questSettings: { english: 'Quest Settings', @@ -4380,7 +6595,11 @@ export const localizations = { brazilian_portuguese: 'Configurações da Missão', tok_pisin: 'Quest Settings', indonesian: 'Pengaturan Quest', - nepali: 'क्वेस्ट सेटिङहरू' + nepali: 'क्वेस्ट सेटिङहरू', + hindi: 'क्वेस्ट सेटिंग्स', + burmese: 'အလုပ်တာဝန် ဆက်တင်များ', + thai: 'การตั้งค่าควสต์', + mandarin: '任务设置' }, questSettingsLoadError: { english: 'Error loading quest settings.', @@ -4388,7 +6607,11 @@ export const localizations = { brazilian_portuguese: 'Erro ao carregar as configurações da quest.', tok_pisin: 'I no inap load quest settings.', indonesian: 'Gagal memuat pengaturan quest.', - nepali: 'क्वेस्ट सेटिङहरू लोड गर्दा त्रुटि।' + nepali: 'क्वेस्ट सेटिङहरू लोड गर्दा त्रुटि।', + hindi: 'क्वेस्ट सेटिंग्स लोड करने में त्रुटि।', + burmese: 'အလုပ်တာဝန် ဆက်တင်များကို ဖွင့်ရာတွင် အမှားအယွင်း။', + thai: 'เกิดข้อผิดพลาดในการโหลดการตั้งค่าควสต์', + mandarin: '加载任务设置时出错。' }, visibleQuestDescription: { english: 'This quest is visible to users', @@ -4396,7 +6619,11 @@ export const localizations = { brazilian_portuguese: 'Esta missão é visível para os usuários', tok_pisin: 'Dispela quest i save long ol user', indonesian: 'Quest ini terlihat oleh pengguna', - nepali: 'यो क्वेस्ट प्रयोगकर्ताहरूलाई देखिन्छ' + nepali: 'यो क्वेस्ट प्रयोगकर्ताहरूलाई देखिन्छ', + hindi: 'यह क्वेस्ट उपयोगकर्ताओं को दिखाई देती है', + burmese: 'ဤအလုပ်တာဝန်သည် အသုံးပြုသူများအတွက် မြင်နိုင်သည်', + thai: 'ควสต์นี้สามารถมองเห็นได้โดยผู้ใช้', + mandarin: '此任务对用户可见' }, invisibleQuestDescription: { english: 'This quest is hidden from users', @@ -4404,7 +6631,11 @@ export const localizations = { brazilian_portuguese: 'Esta missão está oculta dos usuários', tok_pisin: 'Dispela quest i hait long ol user', indonesian: 'Quest ini disembunyikan dari pengguna', - nepali: 'यो क्वेस्ट प्रयोगकर्ताहरूबाट लुकाइएको छ' + nepali: 'यो क्वेस्ट प्रयोगकर्ताहरूबाट लुकाइएको छ', + hindi: 'यह क्वेस्ट उपयोगकर्ताओं से छुपाई गई है', + burmese: 'ဤအလုပ်တာဝန်သည် အသုံးပြုသူများထံမှ ဝှက်ထားသည်', + thai: 'ควสต์นี้ถูกซ่อนจากผู้ใช้', + mandarin: '此任务对用户隐藏' }, activeQuestDescription: { english: 'This quest is available for completion', @@ -4412,7 +6643,11 @@ export const localizations = { brazilian_portuguese: 'Esta missão está disponível para conclusão', tok_pisin: 'Dispela quest i redi long pinisim', indonesian: 'Quest ini tersedia untuk diselesaikan', - nepali: 'यो क्वेस्ट पूरा गर्नको लागि उपलब्ध छ' + nepali: 'यो क्वेस्ट पूरा गर्नको लागि उपलब्ध छ', + hindi: 'यह क्वेस्ट पूरा करने के लिए उपलब्ध है', + burmese: 'ဤအလုပ်တာဝန်သည် ပြီးမြောက်ရန် ရရှိနိုင်သည်', + thai: 'ควสต์นี้พร้อมให้ทำเสร็จ', + mandarin: '此任务可供完成' }, inactiveQuestDescription: { english: 'This quest is temporarily disabled', @@ -4420,7 +6655,11 @@ export const localizations = { brazilian_portuguese: 'Esta missão está temporariamente desabilitada', tok_pisin: 'Dispela quest i stop liklik taim', indonesian: 'Quest ini sementara dinonaktifkan', - nepali: 'यो क्वेस्ट अस्थायी रूपमा असक्षम छ' + nepali: 'यो क्वेस्ट अस्थायी रूपमा असक्षम छ', + hindi: 'यह क्वेस्ट अस्थायी रूप से अक्षम है', + burmese: 'ဤအလုပ်တာဝန်သည် ယာယီပိတ်ထားသည်', + thai: 'ควสต์นี้ถูกปิดใช้งานชั่วคราว', + mandarin: '此任务已暂时禁用' }, questMadeInvisible: { english: 'The quest has been made invisible', @@ -4428,7 +6667,11 @@ export const localizations = { brazilian_portuguese: 'A missão foi tornada invisível', tok_pisin: 'Quest i mekim hait nau', indonesian: 'Quest telah dibuat tidak terlihat', - nepali: 'क्वेस्ट अदृश्य बनाइएको छ' + nepali: 'क्वेस्ट अदृश्य बनाइएको छ', + hindi: 'क्वेस्ट को अदृश्य बना दिया गया है', + burmese: 'အလုပ်တာဝန်ကို မမြင်ရအောင် ပြုလုပ်ထားသည်', + thai: 'ควสต์ถูกทำให้มองไม่เห็น', + mandarin: '任务已设为不可见' }, questMadeVisible: { english: 'The quest has been made visible', @@ -4436,7 +6679,11 @@ export const localizations = { brazilian_portuguese: 'A missão foi tornada visível', tok_pisin: 'Quest i mekim save nau', indonesian: 'Quest telah dibuat terlihat', - nepali: 'क्वेस्ट दृश्य बनाइएको छ' + nepali: 'क्वेस्ट दृश्य बनाइएको छ', + hindi: 'क्वेस्ट को दृश्यमान बना दिया गया है', + burmese: 'အလုပ်တာဝန်ကို မြင်နိုင်အောင် ပြုလုပ်ထားသည်', + thai: 'ควสต์ถูกทำให้มองเห็นได้', + mandarin: '任务已设为可见' }, questMadeInactive: { english: 'The quest has been made inactive', @@ -4444,7 +6691,11 @@ export const localizations = { brazilian_portuguese: 'A missão foi tornada inativa', tok_pisin: 'Quest i mekim stop nau', indonesian: 'Quest telah dibuat tidak aktif', - nepali: 'क्वेस्ट निष्क्रिय बनाइएको छ' + nepali: 'क्वेस्ट निष्क्रिय बनाइएको छ', + hindi: 'क्वेस्ट को निष्क्रिय बना दिया गया है', + burmese: 'အလုပ်တာဝန်ကို ပိတ်ထားသည်', + thai: 'ควสต์ถูกทำให้ไม่ใช้งาน', + mandarin: '任务已设为非活动' }, questMadeActive: { english: 'The quest has been made active', @@ -4452,7 +6703,11 @@ export const localizations = { brazilian_portuguese: 'A missão foi tornada ativa', tok_pisin: 'Quest i mekim active nau', indonesian: 'Quest telah dibuat aktif', - nepali: 'क्वेस्ट सक्रिय बनाइएको छ' + nepali: 'क्वेस्ट सक्रिय बनाइएको छ', + hindi: 'क्वेस्ट को सक्रिय बना दिया गया है', + burmese: 'အလုပ်တာဝန်ကို ဖွင့်ထားသည်', + thai: 'ควสต์ถูกทำให้ใช้งาน', + mandarin: '任务已设为活动' }, failedToUpdateQuestSettings: { english: 'Failed to update quest settings', @@ -4460,7 +6715,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao atualizar as configurações da missão', tok_pisin: 'I no inap update quest settings', indonesian: 'Gagal mengupdate pengaturan quest', - nepali: 'क्वेस्ट सेटिङहरू अपडेट गर्न असफल' + nepali: 'क्वेस्ट सेटिङहरू अपडेट गर्न असफल', + hindi: 'क्वेस्ट सेटिंग्स अपडेट करने में विफल', + burmese: 'အလုပ်တာဝန် ဆက်တင်များကို အပ်ဒိတ်လုပ်ရန် မအောင်မြင်ပါ', + thai: 'ไม่สามารถอัปเดตการตั้งค่าควสต์ได้', + mandarin: '更新任务设置失败' }, loadingAudio: { english: 'Loading audio...', @@ -4468,7 +6727,11 @@ export const localizations = { brazilian_portuguese: 'Carregando áudio...', tok_pisin: 'I loadim audio...', indonesian: 'Memuat audio...', - nepali: 'अडियो लोड गर्दै...' + nepali: 'अडियो लोड गर्दै...', + hindi: 'ऑडियो लोड हो रहा है...', + burmese: 'အသံကို ဖွင့်နေသည်...', + thai: 'กำลังโหลดเสียง...', + mandarin: '正在加载音频...' }, updateAvailable: { english: 'A new update is available!', @@ -4476,7 +6739,11 @@ export const localizations = { brazilian_portuguese: 'Uma nova atualização está disponível!', tok_pisin: 'Nupela update i stap!', indonesian: 'Pembaruan baru tersedia!', - nepali: 'नयाँ अपडेट उपलब्ध छ!' + nepali: 'नयाँ अपडेट उपलब्ध छ!', + hindi: 'एक नया अपडेट उपलब्ध है!', + burmese: 'အပ်ဒိတ်အသစ်တစ်ခု ရရှိနိုင်ပါသည်!', + thai: 'มีการอัปเดตใหม่พร้อมใช้งาน!', + mandarin: '有新更新可用!' }, updateNow: { english: 'Update Now', @@ -4484,7 +6751,11 @@ export const localizations = { brazilian_portuguese: 'Atualizar Agora', tok_pisin: 'Update Nau', indonesian: 'Perbarui Sekarang', - nepali: 'अहिले अपडेट गर्नुहोस्' + nepali: 'अहिले अपडेट गर्नुहोस्', + hindi: 'अभी अपडेट करें', + burmese: 'ယခု အပ်ဒိတ်လုပ်ပါ', + thai: 'อัปเดตตอนนี้', + mandarin: '立即更新' }, updateFailed: { english: 'Update failed', @@ -4492,7 +6763,11 @@ export const localizations = { brazilian_portuguese: 'Atualização falhou', tok_pisin: 'Update i pundaun', indonesian: 'Pembaruan gagal', - nepali: 'अपडेट असफल भयो' + nepali: 'अपडेट असफल भयो', + hindi: 'अपडेट विफल', + burmese: 'အပ်ဒိတ်လုပ်ရန် မအောင်မြင်ပါ', + thai: 'การอัปเดตล้มเหลว', + mandarin: '更新失败' }, updateErrorTryAgain: { english: 'Please try again or dismiss', @@ -4500,7 +6775,11 @@ export const localizations = { brazilian_portuguese: 'Por favor tente novamente ou descarte', tok_pisin: 'Traim gen o rausim', indonesian: 'Silakan coba lagi atau abaikan', - nepali: 'कृपया पुन: प्रयास गर्नुहोस् वा खारेज गर्नुहोस्' + nepali: 'कृपया पुन: प्रयास गर्नुहोस् वा खारेज गर्नुहोस्', + hindi: 'कृपया पुनः प्रयास करें या खारिज करें', + burmese: 'ကျေးဇူးပြု၍ ထပ်မံကြိုးစားပါ သို့မဟုတ် ပယ်ဖျက်ပါ', + thai: 'กรุณาลองอีกครั้งหรือปิด', + mandarin: '请重试或关闭' }, retry: { english: 'Retry', @@ -4508,7 +6787,11 @@ export const localizations = { brazilian_portuguese: 'Tentar novamente', tok_pisin: 'Traim gen', indonesian: 'Coba lagi', - nepali: 'पुन: प्रयास गर्नुहोस्' + nepali: 'पुन: प्रयास गर्नुहोस्', + hindi: 'पुनः प्रयास करें', + burmese: 'ထပ်မံကြိုးစားပါ', + thai: 'ลองอีกครั้ง', + mandarin: '重试' }, enterCommentOptional: { english: 'Enter your comment (optional)', @@ -4516,7 +6799,11 @@ export const localizations = { brazilian_portuguese: 'Escreva seu comentário (opcional)', tok_pisin: 'Raitim comment bilong yu (yu ken o nogat)', indonesian: 'Masukkan komentar Anda (opsional)', - nepali: 'आफ्नो टिप्पणी प्रविष्ट गर्नुहोस् (वैकल्पिक)' + nepali: 'आफ्नो टिप्पणी प्रविष्ट गर्नुहोस् (वैकल्पिक)', + hindi: 'अपनी टिप्पणी दर्ज करें (वैकल्पिक)', + burmese: 'သင်၏မှတ်ချက်ကို ထည့်သွင်းပါ (ရွေးချယ်နိုင်သည်)', + thai: 'ใส่ความคิดเห็นของคุณ (ไม่บังคับ)', + mandarin: '输入您的评论(可选)' }, auth_init_error_title: { english: 'Initialization Error', @@ -4524,7 +6811,11 @@ export const localizations = { brazilian_portuguese: 'Erro de Inicialização', tok_pisin: 'Initialization Error', indonesian: 'Kesalahan Inisialisasi', - nepali: 'सुरुवात त्रुटि' + nepali: 'सुरुवात त्रुटि', + hindi: 'आरंभीकरण त्रुटि', + burmese: 'စတင်ခြင်း အမှား', + thai: 'ข้อผิดพลาดในการเริ่มต้น', + mandarin: '初始化错误' }, auth_init_error_message: { english: @@ -4537,7 +6828,12 @@ export const localizations = { indonesian: 'Gagal menginisialisasi aplikasi. Silakan coba logout dan login kembali.', nepali: - 'एप सुरु गर्न असफल भयो। कृपया लगआउट गरेर पुन: लग इन गर्ने प्रयास गर्नुहोस्।' + 'एप सुरु गर्न असफल भयो। कृपया लगआउट गरेर पुन: लग इन गर्ने प्रयास गर्नुहोस्।', + hindi: + 'ऐप को आरंभ करने में विफल। कृपया लॉग आउट करके फिर से लॉग इन करने का प्रयास करें।', + burmese: 'အက်ပ်ကို စတင်ရန် မအောင်မြင်ပါ။ ကျေးဇူးပြု၍ ထွက်ပြီး ပြန်ဝင်ပါ။', + thai: 'ไม่สามารถเริ่มต้นแอปได้ กรุณาลองออกจากระบบแล้วเข้าสู่ระบบอีกครั้ง', + mandarin: '应用初始化失败。请尝试退出并重新登录。' }, auth_init_error_ok: { english: 'OK', @@ -4545,7 +6841,11 @@ export const localizations = { brazilian_portuguese: 'OK', tok_pisin: 'Orait', indonesian: 'OK', - nepali: 'ठीक छ' + nepali: 'ठीक छ', + hindi: 'ठीक है', + burmese: 'အိုကေ', + thai: 'ตกลง', + mandarin: '确定' }, projectDownloaded: { english: 'Project downloaded', @@ -4553,7 +6853,11 @@ export const localizations = { brazilian_portuguese: 'Projeto baixado', tok_pisin: 'Project i daun pinis', indonesian: 'Proyek diunduh', - nepali: 'प्रोजेक्ट डाउनलोड भयो' + nepali: 'प्रोजेक्ट डाउनलोड भयो', + hindi: 'प्रोजेक्ट डाउनलोड हो गया', + burmese: 'ပရောဂျက်ကို ဒေါင်းလုဒ်လုပ်ပြီးပါပြီ', + thai: 'ดาวน์โหลดโปรเจกต์แล้ว', + mandarin: '项目已下载' }, passwordMustBeAtLeast6Characters: { english: 'Password must be at least 6 characters', @@ -4561,7 +6865,11 @@ export const localizations = { brazilian_portuguese: 'A senha deve ter pelo menos 6 caracteres', tok_pisin: 'Password i mas gat 6 character o moa', indonesian: 'Kata sandi harus minimal 6 karakter', - nepali: 'पासवर्ड कम्तिमा ६ वर्णको हुनुपर्छ' + nepali: 'पासवर्ड कम्तिमा ६ वर्णको हुनुपर्छ', + hindi: 'पासवर्ड कम से कम 6 वर्ण का होना चाहिए', + burmese: 'စကားဝှက်သည် အနည်းဆုံး ၆ လုံး ရှိရမည်', + thai: 'รหัสผ่านต้องมีอย่างน้อย 6 ตัวอักษร', + mandarin: '密码必须至少6个字符' }, passwordUpdateFailed: { english: 'Failed to update password', @@ -4569,7 +6877,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao atualizar a senha', tok_pisin: 'I no inap update password', indonesian: 'Gagal mengupdate kata sandi', - nepali: 'पासवर्ड अपडेट गर्न असफल' + nepali: 'पासवर्ड अपडेट गर्न असफल', + hindi: 'पासवर्ड अपडेट करने में विफल', + burmese: 'စကားဝှက်ကို အပ်ဒိတ်လုပ်ရန် မအောင်မြင်ပါ', + thai: 'ไม่สามารถอัปเดตรหัสผ่านได้', + mandarin: '更新密码失败' }, clearCache: { english: 'Clear Cache', @@ -4577,7 +6889,11 @@ export const localizations = { brazilian_portuguese: 'Limpar cache', tok_pisin: 'Klinim Cache', indonesian: 'Hapus Cache', - nepali: 'क्यास खाली गर्नुहोस्' + nepali: 'क्यास खाली गर्नुहोस्', + hindi: 'कैश साफ करें', + burmese: 'ကက်ရှ်ကို ရှင်းလင်းပါ', + thai: 'ล้างแคช', + mandarin: '清除缓存' }, clearCacheConfirmation: { english: 'Are you sure you want to clear all cached data?', @@ -4586,7 +6902,11 @@ export const localizations = { 'Tem certeza que deseja limpar todos os dados em cache?', tok_pisin: 'Yu sure long klinim olgeta cache data?', indonesian: 'Apakah Anda yakin ingin menghapus semua data cache?', - nepali: 'के तपाईं सबै क्यास डाटा खाली गर्न निश्चित हुनुहुन्छ?' + nepali: 'के तपाईं सबै क्यास डाटा खाली गर्न निश्चित हुनुहुन्छ?', + hindi: 'क्या आप वाकई सभी कैश डेटा साफ करना चाहते हैं?', + burmese: 'ကက်ရှ်ထဲရှိ ဒေတာအားလုံးကို ရှင်းလင်းလိုသည်မှာ သေချာပါသလား?', + thai: 'คุณแน่ใจหรือไม่ว่าต้องการล้างข้อมูลแคชทั้งหมด?', + mandarin: '您确定要清除所有缓存数据吗?' }, cacheClearedSuccess: { english: 'Cache cleared successfully', @@ -4594,7 +6914,11 @@ export const localizations = { brazilian_portuguese: 'Cache limpa com sucesso', tok_pisin: 'Cache i klin gut pinis', indonesian: 'Cache berhasil dihapus', - nepali: 'क्यास सफलतापूर्वक खाली गरियो' + nepali: 'क्यास सफलतापूर्वक खाली गरियो', + hindi: 'कैश सफलतापूर्वक साफ हो गया', + burmese: 'ကက်ရှ်ကို အောင်မြင်စွာ ရှင်းလင်းပြီးပါပြီ', + thai: 'ล้างแคชสำเร็จแล้ว', + mandarin: '缓存清除成功' }, exportRequiresInternet: { english: 'This feature requires an internet connection', @@ -4603,7 +6927,11 @@ export const localizations = { 'Esta funcionalidade requer uma conexão com a internet', tok_pisin: 'Dispela feature i nidim internet connection', indonesian: 'Fitur ini memerlukan koneksi internet', - nepali: 'यो सुविधाको लागि इन्टरनेट जडान आवश्यक छ' + nepali: 'यो सुविधाको लागि इन्टरनेट जडान आवश्यक छ', + hindi: 'इस सुविधा के लिए इंटरनेट कनेक्शन आवश्यक है', + burmese: 'ဤအင်္ဂါရပ်သည် အင်တာနက်ချိတ်ဆက်မှု လိုအပ်သည်', + thai: 'ฟีเจอร์นี้ต้องใช้การเชื่อมต่ออินเทอร์เน็ต', + mandarin: '此功能需要互联网连接' }, exportDataComingSoon: { english: 'Data export feature coming soon', @@ -4611,7 +6939,11 @@ export const localizations = { brazilian_portuguese: 'A exportação de dados está próxima', tok_pisin: 'Data export feature i kam bihain', indonesian: 'Fitur ekspor data segera hadir', - nepali: 'डाटा निर्यात सुविधा छिट्टै आउँदैछ' + nepali: 'डाटा निर्यात सुविधा छिट्टै आउँदैछ', + hindi: 'डेटा निर्यात सुविधा जल्द ही आ रही है', + burmese: 'ဒေတာ တင်ပို့ခြင်း အင်္ဂါရပ် မကြာမီ ရောက်ရှိလာမည်', + thai: 'ฟีเจอร์ส่งออกข้อมูลจะมาเร็วๆ นี้', + mandarin: '数据导出功能即将推出' }, info: { english: 'Info', @@ -4619,7 +6951,11 @@ export const localizations = { brazilian_portuguese: 'Informação', tok_pisin: 'Info', indonesian: 'Info', - nepali: 'जानकारी' + nepali: 'जानकारी', + hindi: 'जानकारी', + burmese: 'အချက်အလက်', + thai: 'ข้อมูล', + mandarin: '信息' }, enableNotifications: { english: 'Enable Notifications', @@ -4627,7 +6963,11 @@ export const localizations = { brazilian_portuguese: 'Habilitar notificações', tok_pisin: 'Onim Notification', indonesian: 'Aktifkan Notifikasi', - nepali: 'सूचनाहरू सक्षम गर्नुहोस्' + nepali: 'सूचनाहरू सक्षम गर्नुहोस्', + hindi: 'सूचनाएं सक्षम करें', + burmese: 'အကြောင်းကြားချက်များကို ဖွင့်ပါ', + thai: 'เปิดใช้งานการแจ้งเตือน', + mandarin: '启用通知' }, notificationsDescription: { english: 'Receive notifications for app updates and important information', @@ -4638,7 +6978,12 @@ export const localizations = { tok_pisin: 'Kisim notification long app update na important information', indonesian: 'Terima notifikasi untuk pembaruan aplikasi dan informasi penting', - nepali: 'एप अपडेट र महत्त्वपूर्ण जानकारीको लागि सूचनाहरू प्राप्त गर्नुहोस्' + nepali: 'एप अपडेट र महत्त्वपूर्ण जानकारीको लागि सूचनाहरू प्राप्त गर्नुहोस्', + hindi: 'ऐप अपडेट और महत्वपूर्ण जानकारी के लिए सूचनाएं प्राप्त करें', + burmese: + 'အက်ပ်အပ်ဒိတ်များနှင့် အရေးကြီးသော အချက်အလက်များအတွက် အကြောင်းကြားချက်များ ရယူပါ', + thai: 'รับการแจ้งเตือนสำหรับการอัปเดตแอปและข้อมูลสำคัญ', + mandarin: '接收应用更新和重要信息的通知' }, contentPreferences: { english: 'Content Preferences', @@ -4646,7 +6991,11 @@ export const localizations = { brazilian_portuguese: 'Preferências de conteúdo', tok_pisin: 'Content Preferences', indonesian: 'Preferensi Konten', - nepali: 'सामग्री प्राथमिकताहरू' + nepali: 'सामग्री प्राथमिकताहरू', + hindi: 'सामग्री प्राथमिकताएं', + burmese: 'အကြောင်းအရာ ဦးစားပေးများ', + thai: 'การตั้งค่าสำหรับเนื้อหา', + mandarin: '内容偏好设置' }, showHiddenContent: { english: 'Show Hidden Content', @@ -4654,7 +7003,11 @@ export const localizations = { brazilian_portuguese: 'Mostrar conteúdo oculto', tok_pisin: 'Soim Hait Content', indonesian: 'Tampilkan Konten Tersembunyi', - nepali: 'लुकेको सामग्री देखाउनुहोस्' + nepali: 'लुकेको सामग्री देखाउनुहोस्', + hindi: 'छुपी हुई सामग्री दिखाएं', + burmese: 'ဝှက်ထားသော အကြောင်းအရာကို ပြပါ', + thai: 'แสดงเนื้อหาที่ซ่อนอยู่', + mandarin: '显示隐藏内容' }, showHiddenContentDescription: { english: 'Allow displaying content that has been marked as invisible', @@ -4664,7 +7017,11 @@ export const localizations = { tok_pisin: 'Larim soim content we ol i makim hait', indonesian: 'Izinkan menampilkan konten yang ditandai sebagai tidak terlihat', - nepali: 'अदृश्य भनी चिन्ह लगाइएको सामग्री प्रदर्शन गर्न अनुमति दिनुहोस्' + nepali: 'अदृश्य भनी चिन्ह लगाइएको सामग्री प्रदर्शन गर्न अनुमति दिनुहोस्', + hindi: 'अदृश्य के रूप में चिह्नित सामग्री प्रदर्शित करने की अनुमति दें', + burmese: 'မမြင်ရအောင် မှတ်သားထားသော အကြောင်းအရာကို ပြခွင့်ပြုပါ', + thai: 'อนุญาตให้แสดงเนื้อหาที่ถูกทำเครื่องหมายว่าไม่สามารถมองเห็นได้', + mandarin: '允许显示标记为不可见的内容' }, dataAndStorage: { english: 'Data & Storage', @@ -4672,7 +7029,11 @@ export const localizations = { brazilian_portuguese: 'Dados e armazenamento', tok_pisin: 'Data na Storage', indonesian: 'Data & Penyimpanan', - nepali: 'डाटा र भण्डारण' + nepali: 'डाटा र भण्डारण', + hindi: 'डेटा और भंडारण', + burmese: 'ဒေတာနှင့် သိုလှောင်မှု', + thai: 'ข้อมูลและการจัดเก็บ', + mandarin: '数据和存储' }, downloadOnWifiOnly: { english: 'Download on WiFi Only', @@ -4680,7 +7041,11 @@ export const localizations = { brazilian_portuguese: 'Baixar apenas em WiFi', tok_pisin: 'Daunim long WiFi tasol', indonesian: 'Unduh hanya di WiFi', - nepali: 'WiFi मा मात्र डाउनलोड गर्नुहोस्' + nepali: 'WiFi मा मात्र डाउनलोड गर्नुहोस्', + hindi: 'केवल WiFi पर डाउनलोड करें', + burmese: 'WiFi တွင်သာ ဒေါင်းလုဒ်လုပ်ပါ', + thai: 'ดาวน์โหลดเฉพาะ WiFi เท่านั้น', + mandarin: '仅在WiFi上下载' }, downloadOnWifiOnlyDescription: { english: 'Only download content when connected to WiFi', @@ -4689,7 +7054,11 @@ export const localizations = { 'Baixar conteúdo apenas quando estiver conectado à WiFi', tok_pisin: 'Daunim content taim yu joinim WiFi tasol', indonesian: 'Hanya unduh konten saat terhubung ke WiFi', - nepali: 'WiFi मा जडान हुँदा मात्र सामग्री डाउनलोड गर्नुहोस्' + nepali: 'WiFi मा जडान हुँदा मात्र सामग्री डाउनलोड गर्नुहोस्', + hindi: 'WiFi से जुड़े होने पर ही सामग्री डाउनलोड करें', + burmese: 'WiFi နှင့် ချိတ်ဆက်ထားသောအခါမှသာ အကြောင်းအရာကို ဒေါင်းလုဒ်လုပ်ပါ', + thai: 'ดาวน์โหลดเนื้อหาเฉพาะเมื่อเชื่อมต่อกับ WiFi', + mandarin: '仅在连接WiFi时下载内容' }, autoBackup: { english: 'Auto Backup', @@ -4697,7 +7066,11 @@ export const localizations = { brazilian_portuguese: 'Backup automático', tok_pisin: 'Auto Backup', indonesian: 'Backup Otomatis', - nepali: 'स्वत: ब्याकअप' + nepali: 'स्वत: ब्याकअप', + hindi: 'स्वचालित बैकअप', + burmese: 'အလိုအလျောက် ဘက်အပ်', + thai: 'สำรองข้อมูลอัตโนมัติ', + mandarin: '自动备份' }, autoBackupDescription: { english: 'Automatically backup your data to the cloud', @@ -4705,7 +7078,11 @@ export const localizations = { brazilian_portuguese: 'Fazer um backup automático dos seus dados na nuvem', tok_pisin: 'Otomatik backup data bilong yu long cloud', indonesian: 'Secara otomatis backup data Anda ke cloud', - nepali: 'आफ्नो डाटा स्वचालित रूपमा क्लाउडमा ब्याकअप गर्नुहोस्' + nepali: 'आफ्नो डाटा स्वचालित रूपमा क्लाउडमा ब्याकअप गर्नुहोस्', + hindi: 'अपने डेटा को क्लाउड में स्वचालित रूप से बैकअप करें', + burmese: 'သင်၏ဒေတာကို ကလောက်ဒ်သို့ အလိုအလျောက် ဘက်အပ်လုပ်ပါ', + thai: 'สำรองข้อมูลของคุณไปยังคลาวด์โดยอัตโนมัติ', + mandarin: '自动将您的数据备份到云端' }, clearCacheDescription: { english: 'Clear all cached data to free up storage space', @@ -4715,7 +7092,11 @@ export const localizations = { 'Limpar todos os dados em cache para liberar espaço de armazenamento', tok_pisin: 'Klinim olgeta cache data long mekim moa storage space', indonesian: 'Hapus semua data cache untuk mengosongkan ruang penyimpanan', - nepali: 'भण्डारण ठाउँ खाली गर्न सबै क्यास डाटा खाली गर्नुहोस्' + nepali: 'भण्डारण ठाउँ खाली गर्न सबै क्यास डाटा खाली गर्नुहोस्', + hindi: 'भंडारण स्थान खाली करने के लिए सभी कैश डेटा साफ करें', + burmese: 'သိုလှောင်မှု နေရာလွတ်ရရန် ကက်ရှ်ထဲရှိ ဒေတာအားလုံးကို ရှင်းလင်းပါ', + thai: 'ล้างข้อมูลแคชทั้งหมดเพื่อเพิ่มพื้นที่จัดเก็บ', + mandarin: '清除所有缓存数据以释放存储空间' }, exportData: { english: 'Export Data', @@ -4723,7 +7104,11 @@ export const localizations = { brazilian_portuguese: 'Exportar dados', tok_pisin: 'Export Data', indonesian: 'Ekspor Data', - nepali: 'डाटा निर्यात गर्नुहोस्' + nepali: 'डाटा निर्यात गर्नुहोस्', + hindi: 'डेटा निर्यात करें', + burmese: 'ဒေတာ တင်ပို့ပါ', + thai: 'ส่งออกข้อมูล', + mandarin: '导出数据' }, exportDataDescription: { english: 'Export your data for backup or transfer', @@ -4731,7 +7116,11 @@ export const localizations = { brazilian_portuguese: 'Exportar seus dados para backup ou transferência', tok_pisin: 'Export data bilong yu long backup o transfer', indonesian: 'Ekspor data Anda untuk backup atau transfer', - nepali: 'ब्याकअप वा स्थानान्तरणको लागि आफ्नो डाटा निर्यात गर्नुहोस्' + nepali: 'ब्याकअप वा स्थानान्तरणको लागि आफ्नो डाटा निर्यात गर्नुहोस्', + hindi: 'बैकअप या स्थानांतरण के लिए अपना डेटा निर्यात करें', + burmese: 'ဘက်အပ်သို့မဟုတ် လွှဲပြောင်းရန်အတွက် သင်၏ဒေတာကို တင်ပို့ပါ', + thai: 'ส่งออกข้อมูลของคุณเพื่อสำรองหรือถ่ายโอน', + mandarin: '导出您的数据以进行备份或传输' }, support: { english: 'Support', @@ -4739,7 +7128,11 @@ export const localizations = { brazilian_portuguese: 'Suporte', tok_pisin: 'Support', indonesian: 'Dukungan', - nepali: 'सहायता' + nepali: 'सहायता', + hindi: 'सहायता', + burmese: 'အကူအညီ', + thai: 'การสนับสนุน', + mandarin: '支持' }, helpCenter: { english: 'Help Center', @@ -4747,7 +7140,11 @@ export const localizations = { brazilian_portuguese: 'Centro de ajuda', tok_pisin: 'Help Center', indonesian: 'Pusat Bantuan', - nepali: 'सहायता केन्द्र' + nepali: 'सहायता केन्द्र', + hindi: 'सहायता केंद्र', + burmese: 'အကူအညီစင်တာ', + thai: 'ศูนย์ช่วยเหลือ', + mandarin: '帮助中心' }, helpCenterComingSoon: { english: 'Help center feature coming soon', @@ -4755,7 +7152,11 @@ export const localizations = { brazilian_portuguese: 'O centro de ajuda está próximo', tok_pisin: 'Help center feature i kam bihain', indonesian: 'Fitur pusat bantuan segera hadir', - nepali: 'सहायता केन्द्र सुविधा छिट्टै आउँदैछ' + nepali: 'सहायता केन्द्र सुविधा छिट्टै आउँदैछ', + hindi: 'सहायता केंद्र सुविधा जल्द ही आ रही है', + burmese: 'အကူအညီစင်တာ အင်္ဂါရပ် မကြာမီ ရောက်ရှိလာမည်', + thai: 'ฟีเจอร์ศูนย์ช่วยเหลือจะมาเร็วๆ นี้', + mandarin: '帮助中心功能即将推出' }, contactSupport: { english: 'Contact Support', @@ -4763,7 +7164,11 @@ export const localizations = { brazilian_portuguese: 'Contatar suporte', tok_pisin: 'Contact Support', indonesian: 'Hubungi Dukungan', - nepali: 'सहायतासँग सम्पर्क गर्नुहोस्' + nepali: 'सहायतासँग सम्पर्क गर्नुहोस्', + hindi: 'सहायता से संपर्क करें', + burmese: 'အကူအညီကို ဆက်သွယ်ပါ', + thai: 'ติดต่อฝ่ายสนับสนุน', + mandarin: '联系支持' }, contactSupportComingSoon: { english: 'Contact support feature coming soon', @@ -4772,7 +7177,11 @@ export const localizations = { 'A funcionalidade de contato com o suporte está próxima', tok_pisin: 'Contact support feature i kam bihain', indonesian: 'Fitur hubungi dukungan segera hadir', - nepali: 'सहायतासँग सम्पर्क सुविधा छिट्टै आउँदैछ' + nepali: 'सहायतासँग सम्पर्क सुविधा छिट्टै आउँदैछ', + hindi: 'सहायता से संपर्क सुविधा जल्द ही आ रही है', + burmese: 'အကူအညီကို ဆက်သွယ်ခြင်း အင်္ဂါရပ် မကြာမီ ရောက်ရှိလာမည်', + thai: 'ฟีเจอร์ติดต่อฝ่ายสนับสนุนจะมาเร็วๆ นี้', + mandarin: '联系支持功能即将推出' }, termsAndConditions: { english: 'Terms & Conditions', @@ -4780,7 +7189,11 @@ export const localizations = { brazilian_portuguese: 'Termos e condições', tok_pisin: 'Terms na Conditions', indonesian: 'Syarat & Ketentuan', - nepali: 'नियम र सर्तहरू' + nepali: 'नियम र सर्तहरू', + hindi: 'नियम और शर्तें', + burmese: 'စည်းမျဉ်းများနှင့် သတ်မှတ်ချက်များ', + thai: 'ข้อกำหนดและเงื่อนไข', + mandarin: '条款和条件' }, termsAndConditionsComingSoon: { english: 'Terms & Conditions feature coming soon', @@ -4788,7 +7201,12 @@ export const localizations = { brazilian_portuguese: 'A funcionalidade de termos e condições está próxima', tok_pisin: 'Terms na Conditions feature i kam bihain', indonesian: 'Fitur Syarat & Ketentuan segera hadir', - nepali: 'नियम र सर्तहरू सुविधा छिट्टै आउँदैछ' + nepali: 'नियम र सर्तहरू सुविधा छिट्टै आउँदैछ', + hindi: 'नियम और शर्तें सुविधा जल्द ही आ रही है', + burmese: + 'စည်းမျဉ်းများနှင့် သတ်မှတ်ချက်များ အင်္ဂါရပ် မကြာမီ ရောက်ရှိလာမည်', + thai: 'ฟีเจอร์ข้อกำหนดและเงื่อนไขจะมาเร็วๆ นี้', + mandarin: '条款和条件功能即将推出' }, experimentalFeatures: { english: 'Experimental Features', @@ -4796,7 +7214,11 @@ export const localizations = { brazilian_portuguese: 'Recursos Experimentais', tok_pisin: 'Experimental Features', indonesian: 'Fitur Eksperimental', - nepali: 'प्रयोगात्मक सुविधाहरू' + nepali: 'प्रयोगात्मक सुविधाहरू', + hindi: 'प्रयोगात्मक सुविधाएं', + burmese: 'စမ်းသပ်အင်္ဂါရပ်များ', + thai: 'ฟีเจอร์ทดลอง', + mandarin: '实验性功能' }, aiSuggestions: { english: 'AI Suggestions', @@ -4804,7 +7226,11 @@ export const localizations = { brazilian_portuguese: 'Sugestões de IA', tok_pisin: 'AI Suggestions', indonesian: 'Saran AI', - nepali: 'एआई सुझावहरू' + nepali: 'एआई सुझावहरू', + hindi: 'एआई सुझाव', + burmese: 'AI အကြံပြုချက်များ', + thai: 'คำแนะนำจาก AI', + mandarin: 'AI建议' }, aiSuggestionsDescription: { english: @@ -4818,7 +7244,12 @@ export const localizations = { indonesian: 'Aktifkan saran terjemahan berbasis AI berdasarkan terjemahan terdekat', nepali: - 'नजिकका अनुवादहरूमा आधारित एआई-संचालित अनुवाद सुझावहरू सक्षम गर्नुहोस्' + 'नजिकका अनुवादहरूमा आधारित एआई-संचालित अनुवाद सुझावहरू सक्षम गर्नुहोस्', + hindi: 'निकटवर्ती अनुवादों के आधार पर AI-संचालित अनुवाद सुझाव सक्षम करें', + burmese: + 'အနီးအနားရှိ ဘာသာပြန်ဆိုချက်များအပေါ် အခြေခံ၍ AI-ပါဝင်သော ဘာသာပြန်ဆိုချက် အကြံပြုချက်များကို ဖွင့်ပါ', + thai: 'เปิดใช้งานคำแนะนำการแปลที่ขับเคลื่อนด้วย AI ตามการแปลที่อยู่ใกล้เคียง', + mandarin: '启用基于附近翻译的AI驱动翻译建议' }, playAll: { english: 'Play All Assets', @@ -4826,7 +7257,11 @@ export const localizations = { brazilian_portuguese: 'Reproduzir Todos os Recursos', tok_pisin: 'Playim Olgeta Assets', indonesian: 'Putar Semua Aset', - nepali: 'सबै एसेटहरू प्ले गर्नुहोस्' + nepali: 'सबै एसेटहरू प्ले गर्नुहोस्', + hindi: 'सभी एसेट चलाएं', + burmese: 'အရင်းအမြစ်အားလုံးကို ဖွင့်ပါ', + thai: 'เล่นทรัพยากรทั้งหมด', + mandarin: '播放所有资源' }, playAllDescription: { english: @@ -4840,7 +7275,13 @@ export const localizations = { indonesian: 'Aktifkan fitur putar semua aset untuk memutar semua aset audio secara berurutan', nepali: - 'सबै अडियो एसेटहरू क्रमशः प्ले गर्न सबै एसेटहरू प्ले गर्ने सुविधा सक्षम गर्नुहोस्' + 'सबै अडियो एसेटहरू क्रमशः प्ले गर्न सबै एसेटहरू प्ले गर्ने सुविधा सक्षम गर्नुहोस्', + hindi: + 'सभी ऑडियो एसेट को क्रम में चलाने के लिए सभी एसेट चलाएं सुविधा सक्षम करें', + burmese: + 'အသံအရင်းအမြစ်အားလုံးကို အစဉ်အတိုင်း ဖွင့်ရန် အရင်းအမြစ်အားလုံးကို ဖွင့်ခြင်း အင်္ဂါရပ်ကို ဖွင့်ပါ', + thai: 'เปิดใช้งานฟีเจอร์เล่นทรัพยากรทั้งหมดเพื่อเล่นทรัพยากรเสียงทั้งหมดตามลำดับ', + mandarin: '启用播放所有资源功能以按顺序播放所有音频资源' }, advanced: { english: 'Advanced', @@ -4848,7 +7289,11 @@ export const localizations = { brazilian_portuguese: 'Avançado', tok_pisin: 'Advanced', indonesian: 'Lanjutan', - nepali: 'उन्नत' + nepali: 'उन्नत', + hindi: 'उन्नत', + burmese: 'အဆင့်မြင့်', + thai: 'ขั้นสูง', + mandarin: '高级' }, debugMode: { english: 'Debug Mode', @@ -4856,7 +7301,11 @@ export const localizations = { brazilian_portuguese: 'Modo de depuração', tok_pisin: 'Debug Mode', indonesian: 'Mode Debug', - nepali: 'डिबग मोड' + nepali: 'डिबग मोड', + hindi: 'डिबग मोड', + burmese: 'ဒီဘတ်ခ်မုဒ်', + thai: 'โหมดดีบัก', + mandarin: '调试模式' }, debugModeDescription: { english: 'Enable debug mode for development features', @@ -4865,7 +7314,11 @@ export const localizations = { 'Habilitar modo de depuração para funcionalidades de desenvolvimento', tok_pisin: 'Onim debug mode long development features', indonesian: 'Aktifkan mode debug untuk fitur pengembangan', - nepali: 'विकास सुविधाहरूको लागि डिबग मोड सक्षम गर्नुहोस्' + nepali: 'विकास सुविधाहरूको लागि डिबग मोड सक्षम गर्नुहोस्', + hindi: 'विकास सुविधाओं के लिए डिबग मोड सक्षम करें', + burmese: 'ဖွံ့ဖြိုးတိုးတက်မှု အင်္ဂါရပ်များအတွက် ဒီဘတ်ခ်မုဒ်ကို ဖွင့်ပါ', + thai: 'เปิดใช้งานโหมดดีบักสำหรับฟีเจอร์การพัฒนา', + mandarin: '为开发功能启用调试模式' }, settingsRequireInternet: { english: 'Some settings require an internet connection', @@ -4874,7 +7327,11 @@ export const localizations = { 'Algumas configurações requerem uma conexão com a internet', tok_pisin: 'Sampela settings i nidim internet connection', indonesian: 'Beberapa pengaturan memerlukan koneksi internet', - nepali: 'केही सेटिङहरूलाई इन्टरनेट जडान आवश्यक छ' + nepali: 'केही सेटिङहरूलाई इन्टरनेट जडान आवश्यक छ', + hindi: 'कुछ सेटिंग्स के लिए इंटरनेट कनेक्शन आवश्यक है', + burmese: 'ဆက်တင်အချို့သည် အင်တာနက်ချိတ်ဆက်မှု လိုအပ်သည်', + thai: 'การตั้งค่าบางอย่างต้องใช้การเชื่อมต่ออินเทอร์เน็ต', + mandarin: '某些设置需要互联网连接' }, internetConnectionRequired: { english: 'Internet connection required', @@ -4882,7 +7339,11 @@ export const localizations = { brazilian_portuguese: 'Conexão com a internet necessária', tok_pisin: 'Internet connection i mas', indonesian: 'Koneksi internet diperlukan', - nepali: 'इन्टरनेट जडान आवश्यक छ' + nepali: 'इन्टरनेट जडान आवश्यक छ', + hindi: 'इंटरनेट कनेक्शन आवश्यक है', + burmese: 'အင်တာနက်ချိတ်ဆက်မှု လိုအပ်သည်', + thai: 'ต้องใช้การเชื่อมต่ออินเทอร์เน็ต', + mandarin: '需要互联网连接' }, clear: { english: 'Clear', @@ -4890,7 +7351,11 @@ export const localizations = { brazilian_portuguese: 'Limpar', tok_pisin: 'Klinim', indonesian: 'Hapus', - nepali: 'खाली गर्नुहोस्' + nepali: 'खाली गर्नुहोस्', + hindi: 'साफ करें', + burmese: 'ရှင်းလင်းပါ', + thai: 'ล้าง', + mandarin: '清除' }, unnamedAsset: { english: 'Unnamed Asset', @@ -4898,7 +7363,11 @@ export const localizations = { brazilian_portuguese: 'Atividade sem nome', tok_pisin: 'Asset i no gat nem', indonesian: 'Asset Tanpa Nama', - nepali: 'नाम नभएको एसेट' + nepali: 'नाम नभएको एसेट', + hindi: 'अनामित एसेट', + burmese: 'အမည်မရှိသော အရင်းအမြစ်', + thai: 'ทรัพยากรที่ไม่มีชื่อ', + mandarin: '未命名资源' }, noAssetSelected: { english: 'No Asset Selected', @@ -4906,7 +7375,11 @@ export const localizations = { brazilian_portuguese: 'Nenhuma atividade selecionada', tok_pisin: 'Yu no makim wanpela asset', indonesian: 'Tidak Ada Asset yang Dipilih', - nepali: 'कुनै एसेट छानिएको छैन' + nepali: 'कुनै एसेट छानिएको छैन', + hindi: 'कोई एसेट चयनित नहीं', + burmese: 'အရင်းအမြစ် ရွေးချယ်ထားခြင်း မရှိပါ', + thai: 'ไม่ได้เลือกทรัพยากร', + mandarin: '未选择资源' }, assetNotAvailableOffline: { english: 'Asset not available offline', @@ -4914,7 +7387,11 @@ export const localizations = { brazilian_portuguese: 'A atividade não está disponível offline', tok_pisin: 'Asset i no stap taim i no gat internet', indonesian: 'Asset tidak tersedia offline', - nepali: 'एसेट अफलाइन उपलब्ध छैन' + nepali: 'एसेट अफलाइन उपलब्ध छैन', + hindi: 'एसेट ऑफलाइन उपलब्ध नहीं है', + burmese: 'အရင်းအမြစ်သည် အော့ဖ်လိုင်း ရရှိနိုင်မည်မဟုတ်ပါ', + thai: 'ทรัพยากรไม่พร้อมใช้งานแบบออฟไลน์', + mandarin: '资源离线不可用' }, cloudError: { english: 'Cloud error: {error}', @@ -4922,7 +7399,11 @@ export const localizations = { brazilian_portuguese: 'Erro na nuvem: {error}', tok_pisin: 'Cloud error: {error}', indonesian: 'Kesalahan cloud: {error}', - nepali: 'क्लाउड त्रुटि: {error}' + nepali: 'क्लाउड त्रुटि: {error}', + hindi: 'क्लाउड त्रुटि: {error}', + burmese: 'ကလောက်ဒ် အမှား: {error}', + thai: 'ข้อผิดพลาดของคลาวด์: {error}', + mandarin: '云错误: {error}' }, assetNotFoundOnline: { english: 'Asset not found online', @@ -4930,7 +7411,11 @@ export const localizations = { brazilian_portuguese: 'A atividade não foi encontrada online', tok_pisin: 'Asset i no stap long internet', indonesian: 'Asset tidak ditemukan online', - nepali: 'एसेट अनलाइन फेला परेन' + nepali: 'एसेट अनलाइन फेला परेन', + hindi: 'एसेट ऑनलाइन नहीं मिला', + burmese: 'အရင်းအမြစ်ကို အွန်လိုင်းတွင် မတွေ့ရှိပါ', + thai: 'ไม่พบทรัพยากรออนไลน์', + mandarin: '在线未找到资源' }, trySwitchingToCloudDataSource: { english: 'Try switching to Cloud data source above', @@ -4938,7 +7423,11 @@ export const localizations = { brazilian_portuguese: 'Tente mudar para a fonte de dados na nuvem', tok_pisin: 'Traim senisim long Cloud data source antap', indonesian: 'Coba beralih ke sumber data Cloud di atas', - nepali: 'माथि क्लाउड डाटा स्रोतमा स्विच गर्ने प्रयास गर्नुहोस्' + nepali: 'माथि क्लाउड डाटा स्रोतमा स्विच गर्ने प्रयास गर्नुहोस्', + hindi: 'ऊपर क्लाउड डेटा स्रोत पर स्विच करने का प्रयास करें', + burmese: 'အထက်ရှိ ကလောက်ဒ် ဒေတာ အရင်းအမြစ်သို့ ပြောင်းရန် ကြိုးစားပါ', + thai: 'ลองเปลี่ยนไปใช้แหล่งข้อมูลคลาวด์ด้านบน', + mandarin: '尝试切换到上方的云数据源' }, trySwitchingToOfflineDataSource: { english: 'Try switching to Offline data source above', @@ -4946,7 +7435,11 @@ export const localizations = { brazilian_portuguese: 'Tente mudar para a fonte de datos offline', tok_pisin: 'Traim senisim long Offline data source antap', indonesian: 'Coba beralih ke sumber data Offline di atas', - nepali: 'माथि अफलाइन डाटा स्रोतमा स्विच गर्ने प्रयास गर्नुहोस्' + nepali: 'माथि अफलाइन डाटा स्रोतमा स्विच गर्ने प्रयास गर्नुहोस्', + hindi: 'ऊपर ऑफलाइन डेटा स्रोत पर स्विच करने का प्रयास करें', + burmese: 'အထက်ရှိ အော့ဖ်လိုင်း ဒေတာ အရင်းအမြစ်သို့ ပြောင်းရန် ကြိုးစားပါ', + thai: 'ลองเปลี่ยนไปใช้แหล่งข้อมูลออฟไลน์ด้านบน', + mandarin: '尝试切换到上方的离线数据源' }, assetMayNotBeSynchronized: { english: 'This asset may not be synchronized or may not exist', @@ -4955,7 +7448,11 @@ export const localizations = { 'Esta atividade pode não estar sincronizada ou pode não existir', tok_pisin: 'Dispela asset i no sync o i no stap', indonesian: 'Asset ini mungkin tidak tersinkronisasi atau tidak ada', - nepali: 'यो एसेट सिंक्रोनाइज नभएको वा अवस्थित नहुन सक्छ' + nepali: 'यो एसेट सिंक्रोनाइज नभएको वा अवस्थित नहुन सक्छ', + hindi: 'यह एसेट सिंक्रनाइज़ नहीं हो सकता है या मौजूद नहीं हो सकता है', + burmese: 'ဤအရင်းအမြစ်သည် စင့်ချရန်မလိုအပ်သော သို့မဟုတ် မရှိနိုင်ပါ', + thai: 'ทรัพยากรนี้อาจไม่ได้ซิงค์หรืออาจไม่มีอยู่', + mandarin: '此资源可能未同步或可能不存在' }, noContentAvailable: { english: 'No content available', @@ -4963,7 +7460,11 @@ export const localizations = { brazilian_portuguese: 'Nenhum conteúdo disponível', tok_pisin: 'I no gat content', indonesian: 'Tidak ada konten tersedia', - nepali: 'कुनै सामग्री उपलब्ध छैन' + nepali: 'कुनै सामग्री उपलब्ध छैन', + hindi: 'कोई सामग्री उपलब्ध नहीं है', + burmese: 'အကြောင်းအရာ မရရှိနိုင်ပါ', + thai: 'ไม่มีเนื้อหา', + mandarin: '无可用内容' }, audioReady: { english: 'Audio ready', @@ -4971,7 +7472,11 @@ export const localizations = { brazilian_portuguese: 'Áudio pronto', tok_pisin: 'Audio i redi', indonesian: 'Audio siap', - nepali: 'अडियो तयार छ' + nepali: 'अडियो तयार छ', + hindi: 'ऑडियो तैयार', + burmese: 'အသံ အဆင်သင့်ဖြစ်ပါပြီ', + thai: 'เสียงพร้อม', + mandarin: '音频就绪' }, audioNotAvailable: { english: 'Audio not available', @@ -4979,7 +7484,11 @@ export const localizations = { brazilian_portuguese: 'Áudio não disponível', tok_pisin: 'Audio i no stap', indonesian: 'Audio tidak tersedia', - nepali: 'अडियो उपलब्ध छैन' + nepali: 'अडियो उपलब्ध छैन', + hindi: 'ऑडियो उपलब्ध नहीं है', + burmese: 'အသံ မရရှိနိုင်ပါ', + thai: 'เสียงไม่พร้อมใช้งาน', + mandarin: '音频不可用' }, imagesAvailable: { english: 'Images available', @@ -4987,7 +7496,11 @@ export const localizations = { brazilian_portuguese: 'Imagens disponíveis', tok_pisin: 'Ol piksa i stap', indonesian: 'Gambar tersedia', - nepali: 'तस्बिरहरू उपलब्ध छन्' + nepali: 'तस्बिरहरू उपलब्ध छन्', + hindi: 'छवियां उपलब्ध हैं', + burmese: 'ရုပ်ပုံများ ရရှိနိုင်ပါသည်', + thai: 'มีรูปภาพ', + mandarin: '图像可用' }, language: { english: 'Language', @@ -4995,7 +7508,11 @@ export const localizations = { brazilian_portuguese: 'Idioma', tok_pisin: 'Tokples', indonesian: 'Bahasa', - nepali: 'भाषा' + nepali: 'भाषा', + hindi: 'भाषा', + burmese: 'ဘာသာစကား', + thai: 'ภาษา', + mandarin: '语言' }, template: { english: 'Template', @@ -5003,7 +7520,11 @@ export const localizations = { brazilian_portuguese: 'Plantilla', tok_pisin: 'Template', indonesian: 'Template', - nepali: 'टेम्प्लेट' + nepali: 'टेम्प्लेट', + hindi: 'टेम्प्लेट', + burmese: 'ပုံစံ', + thai: 'เทมเพลต', + mandarin: '模板' }, // template options bible: { @@ -5012,7 +7533,11 @@ export const localizations = { brazilian_portuguese: 'Bíblia', tok_pisin: 'Bible', indonesian: 'Alkitab', - nepali: 'बाइबल' + nepali: 'बाइबल', + hindi: 'बाइबल', + burmese: 'သမ္မာကျမ်းစာ', + thai: 'พระคัมภีร์', + mandarin: '圣经' }, unstructured: { english: 'Unstructured', @@ -5020,7 +7545,11 @@ export const localizations = { brazilian_portuguese: 'Não estruturado', tok_pisin: 'Unstructured', indonesian: 'Tidak terstruktur', - nepali: 'संरचना नभएको' + nepali: 'संरचना नभएको', + hindi: 'असंरचित', + burmese: 'ဖွဲ့စည်းမှု မရှိသော', + thai: 'ไม่มีโครงสร้าง', + mandarin: '非结构化' }, audioTracks: { english: 'Audio tracks', @@ -5028,7 +7557,11 @@ export const localizations = { brazilian_portuguese: 'Pistas de áudio', tok_pisin: 'Ol audio track', indonesian: 'Trek audio', - nepali: 'अडियो ट्र्याकहरू' + nepali: 'अडियो ट्र्याकहरू', + hindi: 'ऑडियो ट्रैक', + burmese: 'အသံထွက်လမ်းကြောင်းများ', + thai: 'แทร็กเสียง', + mandarin: '音频轨道' }, membersOnly: { english: 'Members Only', @@ -5036,7 +7569,11 @@ export const localizations = { brazilian_portuguese: 'Só para membros', tok_pisin: 'Member tasol', indonesian: 'Khusus Anggota', - nepali: 'सदस्यहरूको लागि मात्र' + nepali: 'सदस्यहरूको लागि मात्र', + hindi: 'केवल सदस्य', + burmese: 'အဖွဲ့ဝင်များသာ', + thai: 'สมาชิกเท่านั้น', + mandarin: '仅限成员' }, cloud: { english: 'Cloud', @@ -5044,7 +7581,11 @@ export const localizations = { brazilian_portuguese: 'Nuvem', tok_pisin: 'Cloud', indonesian: 'Cloud', - nepali: 'क्लाउड' + nepali: 'क्लाउड', + hindi: 'क्लाउड', + burmese: 'Cloud', + thai: 'คลาวด์', + mandarin: '云端' }, syncing: { english: 'Syncing', @@ -5052,7 +7593,11 @@ export const localizations = { brazilian_portuguese: 'Sincronizando', tok_pisin: 'I sync', indonesian: 'Sinkronisasi', - nepali: 'सिङ्क गर्दै' + nepali: 'सिङ्क गर्दै', + hindi: 'सिंक हो रहा है', + burmese: 'အင်ချိန်နေသည်', + thai: 'กำลังซิงค์', + mandarin: '正在同步' }, synced: { english: 'Synced', @@ -5060,7 +7605,11 @@ export const localizations = { brazilian_portuguese: 'Sincronizado', tok_pisin: 'Sync pinis', indonesian: 'Tersinkronisasi', - nepali: 'सिङ्क भयो' + nepali: 'सिङ्क भयो', + hindi: 'सिंक हो गया', + burmese: 'အင်ချိန်ပြီးပြီ', + thai: 'ซิงค์แล้ว', + mandarin: '已同步' }, questSyncedToCloud: { english: 'Quest is synced to cloud', @@ -5068,7 +7617,11 @@ export const localizations = { brazilian_portuguese: 'A missão está sincronizada na nuvem', tok_pisin: 'Quest i sync pinis long cloud', indonesian: 'Quest telah disinkronkan ke cloud', - nepali: 'क्वेस्ट क्लाउडमा सिङ्क भएको छ' + nepali: 'क्वेस्ट क्लाउडमा सिङ्क भएको छ', + hindi: 'क्वेस्ट क्लाउड में सिंक हो गया है', + burmese: 'Quest သည် cloud သို့ အင်ချိန်ပြီးပြီ', + thai: 'เควสต์ถูกซิงค์ไปยังคลาวด์แล้ว', + mandarin: '任务已同步到云端' }, failed: { english: 'Failed', @@ -5076,7 +7629,11 @@ export const localizations = { brazilian_portuguese: 'Falhado', tok_pisin: 'I pail', indonesian: 'Gagal', - nepali: 'असफल भयो' + nepali: 'असफल भयो', + hindi: 'असफल', + burmese: 'ကျရှုံးသည်', + thai: 'ล้มเหลว', + mandarin: '失败' }, state: { english: 'State', @@ -5084,7 +7641,11 @@ export const localizations = { brazilian_portuguese: 'Estado', tok_pisin: 'State', indonesian: 'Status', - nepali: 'अवस्था' + nepali: 'अवस्था', + hindi: 'अवस्था', + burmese: 'အခြေအနေ', + thai: 'สถานะ', + mandarin: '状态' }, noQuestSelected: { english: 'No Quest Selected', @@ -5092,7 +7653,11 @@ export const localizations = { brazilian_portuguese: 'Nenhum projeto selecionado', tok_pisin: 'Yu no makim wanpela quest', indonesian: 'Tidak Ada Quest yang Dipilih', - nepali: 'कुनै क्वेस्ट छानिएको छैन' + nepali: 'कुनै क्वेस्ट छानिएको छैन', + hindi: 'कोई क्वेस्ट चयनित नहीं', + burmese: 'Quest ကို မရွေးချယ်ထားပါ', + thai: 'ไม่ได้เลือกเควสต์', + mandarin: '未选择任务' }, liveAttachmentStates: { english: 'Live Attachment States', @@ -5100,7 +7665,11 @@ export const localizations = { brazilian_portuguese: 'Estados de anexos em tempo real', tok_pisin: 'Live Attachment States', indonesian: 'Status Lampiran Langsung', - nepali: 'प्रत्यक्ष संलग्नक अवस्थाहरू' + nepali: 'प्रत्यक्ष संलग्नक अवस्थाहरू', + hindi: 'लाइव संलग्नक अवस्थाएं', + burmese: 'တိုက်ရိုက်ပူးတွဲအခြေအနေများ', + thai: 'สถานะไฟล์แนบแบบสด', + mandarin: '实时附件状态' }, searching: { english: 'Searching', @@ -5108,7 +7677,11 @@ export const localizations = { brazilian_portuguese: 'Buscando', tok_pisin: 'I painim', indonesian: 'Mencari', - nepali: 'खोज्दै' + nepali: 'खोज्दै', + hindi: 'खोज रहे हैं', + burmese: 'ရှာဖွေနေသည်', + thai: 'กำลังค้นหา', + mandarin: '正在搜索' }, translationSubmittedSuccessfully: { english: 'Translation submitted successfully', @@ -5116,7 +7689,11 @@ export const localizations = { brazilian_portuguese: 'Tradução enviada com sucesso', tok_pisin: 'Translation i go gut pinis', indonesian: 'Terjemahan berhasil dikirim', - nepali: 'अनुवाद सफलतापूर्वक पेश गरियो' + nepali: 'अनुवाद सफलतापूर्वक पेश गरियो', + hindi: 'अनुवाद सफलतापूर्वक सबमिट किया गया', + burmese: 'ဘာသာပြန်ချက်ကို အောင်မြင်စွာ တင်သွင်းပြီးပြီ', + thai: 'ส่งคำแปลสำเร็จแล้ว', + mandarin: '翻译提交成功' }, transcriptionSubmittedSuccessfully: { english: 'Transcription submitted successfully', @@ -5124,7 +7701,11 @@ export const localizations = { brazilian_portuguese: 'Transcrição enviada com sucesso', tok_pisin: 'Transcription i go gut pinis', indonesian: 'Transkripsi berhasil dikirim', - nepali: 'ट्रान्सक्रिप्सन सफलतापूर्वक पेश गरियो' + nepali: 'ट्रान्सक्रिप्सन सफलतापूर्वक पेश गरियो', + hindi: 'ट्रांसक्रिप्शन सफलतापूर्वक जमा हो गया', + burmese: 'စာလုံးပေါင်းကို အောင်မြင်စွာ တင်မြှောက်ပြီးပါပြီ', + thai: 'ส่งการถอดความสำเร็จแล้ว', + mandarin: '转录已成功提交' }, text: { english: 'Text', @@ -5132,7 +7713,11 @@ export const localizations = { brazilian_portuguese: 'Texto', tok_pisin: 'Text', indonesian: 'Teks', - nepali: 'पाठ' + nepali: 'पाठ', + hindi: 'पाठ', + burmese: 'စာသား', + thai: 'ข้อความ', + mandarin: '文本' }, audio: { english: 'Audio', @@ -5140,7 +7725,11 @@ export const localizations = { brazilian_portuguese: 'Áudio', tok_pisin: 'Audio', indonesian: 'Audio', - nepali: 'अडियो' + nepali: 'अडियो', + hindi: 'ऑडियो', + burmese: 'အသံ', + thai: 'เสียง', + mandarin: '音频' }, targetLanguage: { english: 'Target Language', @@ -5148,7 +7737,11 @@ export const localizations = { brazilian_portuguese: 'Idioma de destino', tok_pisin: 'Target Tokples', indonesian: 'Bahasa Target', - nepali: 'लक्षित भाषा' + nepali: 'लक्षित भाषा', + hindi: 'लक्ष्य भाषा', + burmese: 'ပစ်မှတ်ဘာသာစကား', + thai: 'ภาษาปลายทาง', + mandarin: '目标语言' }, sourceLanguage: { english: 'Source Language', @@ -5156,7 +7749,11 @@ export const localizations = { brazilian_portuguese: 'Idioma de origem', tok_pisin: 'Source Tokples', indonesian: 'Bahasa Sumber', - nepali: 'स्रोत भाषा' + nepali: 'स्रोत भाषा', + hindi: 'स्रोत भाषा', + burmese: 'အရင်းအမြစ်ဘာသာစကား', + thai: 'ภาษาต้นทาง', + mandarin: '源语言' }, your: { english: 'Your', @@ -5164,7 +7761,11 @@ export const localizations = { brazilian_portuguese: 'Seu', tok_pisin: 'Bilong yu', indonesian: 'Anda', - nepali: 'तपाईंको' + nepali: 'तपाईंको', + hindi: 'आपका', + burmese: 'သင်၏', + thai: 'ของคุณ', + mandarin: '您的' }, translation: { english: 'Translation', @@ -5172,7 +7773,11 @@ export const localizations = { brazilian_portuguese: 'Tradução', tok_pisin: 'Translation', indonesian: 'Terjemahan', - nepali: 'अनुवाद' + nepali: 'अनुवाद', + hindi: 'अनुवाद', + burmese: 'ဘာသာပြန်ဆိုချက်', + thai: 'การแปล', + mandarin: '翻译' }, readyToSubmit: { english: 'Ready to submit', @@ -5180,7 +7785,11 @@ export const localizations = { brazilian_portuguese: 'Pronto para enviar', tok_pisin: 'Redi long salim', indonesian: 'Siap untuk dikirim', - nepali: 'पेश गर्न तयार' + nepali: 'पेश गर्न तयार', + hindi: 'जमा करने के लिए तैयार', + burmese: 'တင်မြှောက်ရန် အဆင်သင့်', + thai: 'พร้อมส่ง', + mandarin: '准备提交' }, online: { english: 'Online', @@ -5188,7 +7797,11 @@ export const localizations = { brazilian_portuguese: 'Online', tok_pisin: 'Online', indonesian: 'Online', - nepali: 'अनलाइन' + nepali: 'अनलाइन', + hindi: 'ऑनलाइन', + burmese: 'အွန်လိုင်း', + thai: 'ออนไลน์', + mandarin: '在线' }, allProjects: { english: 'All Projects', @@ -5196,7 +7809,11 @@ export const localizations = { brazilian_portuguese: 'Todos os projetos', tok_pisin: 'Olgeta Project', indonesian: 'Semua Proyek', - nepali: 'सबै प्रोजेक्टहरू' + nepali: 'सबै प्रोजेक्टहरू', + hindi: 'सभी परियोजनाएं', + burmese: 'စီမံကိန်းအားလုံး', + thai: 'โครงการทั้งหมด', + mandarin: '所有项目' }, searchProjects: { english: 'Search projects...', @@ -5204,7 +7821,11 @@ export const localizations = { brazilian_portuguese: 'Buscar projetos...', tok_pisin: 'Painim ol project...', indonesian: 'Cari proyek...', - nepali: 'प्रोजेक्टहरू खोज्नुहोस्...' + nepali: 'प्रोजेक्टहरू खोज्नुहोस्...', + hindi: 'परियोजनाएं खोजें...', + burmese: 'စီမံကိန်းများ ရှာပါ...', + thai: 'ค้นหาโครงการ...', + mandarin: '搜索项目...' }, noProjectSelected: { english: 'No Project Selected', @@ -5212,7 +7833,11 @@ export const localizations = { brazilian_portuguese: 'Nenhum projeto selecionado', tok_pisin: 'Yu no makim wanpela project', indonesian: 'Tidak Ada Proyek yang Dipilih', - nepali: 'कुनै प्रोजेक्ट छानिएको छैन' + nepali: 'कुनै प्रोजेक्ट छानिएको छैन', + hindi: 'कोई परियोजना चयनित नहीं', + burmese: 'စီမံကိန်း ရွေးချယ်ထားခြင်း မရှိပါ', + thai: 'ไม่ได้เลือกโครงการ', + mandarin: '未选择项目' }, noQuestsFound: { english: 'No quests found', @@ -5220,7 +7845,11 @@ export const localizations = { brazilian_portuguese: 'Nenhuma missão encontrada', tok_pisin: 'I no gat quest', indonesian: 'Tidak ada quest ditemukan', - nepali: 'कुनै क्वेस्ट फेला परेन' + nepali: 'कुनै क्वेस्ट फेला परेन', + hindi: 'कोई क्वेस्ट नहीं मिली', + burmese: 'ခရီးစဉ် မတွေ့ရှိပါ', + thai: 'ไม่พบภารกิจ', + mandarin: '未找到任务' }, noQuestsAvailable: { english: 'No quests available', @@ -5228,7 +7857,11 @@ export const localizations = { brazilian_portuguese: 'Nenhuma missão disponível', tok_pisin: 'I no gat quest long usim', indonesian: 'Tidak ada quest tersedia', - nepali: 'कुनै क्वेस्ट उपलब्ध छैन' + nepali: 'कुनै क्वेस्ट उपलब्ध छैन', + hindi: 'कोई क्वेस्ट उपलब्ध नहीं', + burmese: 'ခရီးစဉ် မရှိပါ', + thai: 'ไม่มีภารกิจ', + mandarin: '没有可用的任务' }, pleaseLogInToVote: { english: 'Please log in to vote', @@ -5236,7 +7869,11 @@ export const localizations = { brazilian_portuguese: 'Por favor, faça login para votar', tok_pisin: 'Plis login pastaim long vote', indonesian: 'Silakan login untuk memilih', - nepali: 'कृपया मतदान गर्न लग इन गर्नुहोस्' + nepali: 'कृपया मतदान गर्न लग इन गर्नुहोस्', + hindi: 'कृपया मतदान करने के लिए लॉग इन करें', + burmese: 'မဲပေးရန် ကျေးဇူးပြု၍ လော့ဂ်အင်လုပ်ပါ', + thai: 'กรุณาเข้าสู่ระบบเพื่อลงคะแนน', + mandarin: '请登录以投票' }, pleaseLogInToTranscribe: { english: 'Please log in to transcribe audio', @@ -5244,7 +7881,11 @@ export const localizations = { brazilian_portuguese: 'Por favor, faça login para transcrever áudio', tok_pisin: 'Plis login pastaim long transcribe audio', indonesian: 'Silakan login untuk mentranskripsi audio', - nepali: 'कृपया अडियो ट्रान्सक्राइब गर्न लग इन गर्नुहोस्' + nepali: 'कृपया अडियो ट्रान्सक्राइब गर्न लग इन गर्नुहोस्', + hindi: 'कृपया ऑडियो ट्रांसक्राइब करने के लिए लॉग इन करें', + burmese: 'အသံ ရေးမှတ်ရန် ကျေးဇူးပြု၍ လော့ဂ်အင်လုပ်ပါ', + thai: 'กรุณาเข้าสู่ระบบเพื่อถอดความเสียง', + mandarin: '请登录以转录音频' }, transcriptionFailed: { english: 'Failed to transcribe audio. Please try again.', @@ -5253,7 +7894,11 @@ export const localizations = { 'Falha ao transcrever áudio. Por favor, tente novamente.', tok_pisin: 'I no inap transcribe audio. Plis traim gen.', indonesian: 'Gagal mentranskripsi audio. Silakan coba lagi.', - nepali: 'अडियो ट्रान्सक्राइब गर्न असफल। कृपया पुन: प्रयास गर्नुहोस्।' + nepali: 'अडियो ट्रान्सक्राइब गर्न असफल। कृपया पुन: प्रयास गर्नुहोस्।', + hindi: 'ऑडियो ट्रांसक्राइब करने में विफल। कृपया पुनः प्रयास करें।', + burmese: 'အသံ ရေးမှတ်ရန် မအောင်မြင်ပါ။ ကျေးဇူးပြု၍ ထပ်မံကြိုးစားပါ။', + thai: 'ถอดความเสียงไม่สำเร็จ กรุณาลองอีกครั้ง', + mandarin: '转录音频失败。请重试。' }, yourTranscriptionHasBeenSubmitted: { english: 'Your transcription has been submitted', @@ -5261,7 +7906,11 @@ export const localizations = { brazilian_portuguese: 'Sua transcrição foi enviada', tok_pisin: 'Transcription bilong yu i go pinis', indonesian: 'Transkripsi Anda telah dikirim', - nepali: 'तपाईंको ट्रान्सक्रिप्शन पेश गरिएको छ' + nepali: 'तपाईंको ट्रान्सक्रिप्शन पेश गरिएको छ', + hindi: 'आपका ट्रांसक्रिप्शन जमा कर दिया गया है', + burmese: 'သင်၏ စာလုံးပေါင်းကို တင်မြှောက်ပြီးပါပြီ', + thai: 'การถอดความของคุณถูกส่งแล้ว', + mandarin: '您的转录已提交' }, failedToCreateTranscription: { english: 'Failed to create transcription', @@ -5269,7 +7918,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao criar a transcrição', tok_pisin: 'I no inap mekim transcription', indonesian: 'Gagal membuat transkripsi', - nepali: 'ट्रान्सक्रिप्शन सिर्जना गर्न असफल' + nepali: 'ट्रान्सक्रिप्शन सिर्जना गर्न असफल', + hindi: 'ट्रांसक्रिप्शन बनाने में विफल', + burmese: 'စာလုံးပေါင်း ဖန်တီးရန် မအောင်မြင်ပါ', + thai: 'สร้างการถอดความไม่สำเร็จ', + mandarin: '创建转录失败' }, enterYourTranscription: { english: 'Enter your transcription', @@ -5277,7 +7930,11 @@ export const localizations = { brazilian_portuguese: 'Digite sua transcrição', tok_pisin: 'Raitim transcription bilong yu', indonesian: 'Masukkan transkripsi Anda', - nepali: 'आफ्नो ट्रान्सक्रिप्शन प्रविष्ट गर्नुहोस्' + nepali: 'आफ्नो ट्रान्सक्रिप्शन प्रविष्ट गर्नुहोस्', + hindi: 'अपना ट्रांसक्रिप्शन दर्ज करें', + burmese: 'သင်၏ စာလုံးပေါင်းကို ထည့်သွင်းပါ', + thai: 'ป้อนการถอดความของคุณ', + mandarin: '输入您的转录' }, submitTranscription: { english: 'Submit Transcription', @@ -5285,7 +7942,11 @@ export const localizations = { brazilian_portuguese: 'Enviar transcrição', tok_pisin: 'Salim Transcription', indonesian: 'Kirim Transkripsi', - nepali: 'ट्रान्सक्रिप्शन पेश गर्नुहोस्' + nepali: 'ट्रान्सक्रिप्शन पेश गर्नुहोस्', + hindi: 'ट्रांसक्रिप्शन जमा करें', + burmese: 'စာလုံးပေါင်း တင်မြှောက်ပါ', + thai: 'ส่งการถอดความ', + mandarin: '提交转录' }, good: { english: 'Good', @@ -5293,7 +7954,11 @@ export const localizations = { brazilian_portuguese: 'Bom', tok_pisin: 'Gut', indonesian: 'Bagus', - nepali: 'राम्रो' + nepali: 'राम्रो', + hindi: 'अच्छा', + burmese: 'ကောင်းပါသည်', + thai: 'ดี', + mandarin: '好' }, needsWork: { english: 'Needs Work', @@ -5301,7 +7966,11 @@ export const localizations = { brazilian_portuguese: 'Precisa de trabalho', tok_pisin: 'I nidim wok moa', indonesian: 'Perlu Perbaikan', - nepali: 'सुधार चाहिन्छ' + nepali: 'सुधार चाहिन्छ', + hindi: 'सुधार की आवश्यकता', + burmese: 'ပြင်ဆင်ရန် လိုအပ်သည်', + thai: 'ต้องปรับปรุง', + mandarin: '需要改进' }, pleaseLogInToVoteOnTranslations: { english: 'Please log in to vote on translations', @@ -5309,7 +7978,11 @@ export const localizations = { brazilian_portuguese: 'Por favor, faça login para votar em traduções', tok_pisin: 'Plis login pastaim long vote long ol translation', indonesian: 'Silakan login untuk memilih terjemahan', - nepali: 'कृपया अनुवादहरूमा मतदान गर्न लग इन गर्नुहोस्' + nepali: 'कृपया अनुवादहरूमा मतदान गर्न लग इन गर्नुहोस्', + hindi: 'कृपया अनुवादों पर मतदान करने के लिए लॉग इन करें', + burmese: 'ဘာသာပြန်ဆိုချက်များတွင် မဲပေးရန် ကျေးဇူးပြု၍ လော့ဂ်အင်လုပ်ပါ', + thai: 'กรุณาเข้าสู่ระบบเพื่อลงคะแนนการแปล', + mandarin: '请登录以对翻译投票' }, translationNotFound: { english: 'Translation not found', @@ -5317,7 +7990,11 @@ export const localizations = { brazilian_portuguese: 'Tradução não encontrada', tok_pisin: 'Translation i no stap', indonesian: 'Terjemahan tidak ditemukan', - nepali: 'अनुवाद फेला परेन' + nepali: 'अनुवाद फेला परेन', + hindi: 'अनुवाद नहीं मिला', + burmese: 'ဘာသာပြန်ဆိုချက် မတွေ့ရှိပါ', + thai: 'ไม่พบการแปล', + mandarin: '未找到翻译' }, noTranslationsYet: { english: 'No translations yet. Be the first to translate!', @@ -5325,7 +8002,11 @@ export const localizations = { brazilian_portuguese: 'Nenhuma tradução ainda. Seja o primeiro a traduzir!', tok_pisin: 'I no gat translation yet. Yu ken namba wan long translate!', indonesian: 'Belum ada terjemahan. Jadilah yang pertama menerjemahkan!', - nepali: 'अहिलेसम्म कुनै अनुवाद छैन। पहिलो अनुवादक बन्नुहोस्!' + nepali: 'अहिलेसम्म कुनै अनुवाद छैन। पहिलो अनुवादक बन्नुहोस्!', + hindi: 'अभी तक कोई अनुवाद नहीं। पहले अनुवादक बनें!', + burmese: 'အခုထိ ဘာသာပြန်ဆိုချက် မရှိသေးပါ။ ပထမဆုံး ဘာသာပြန်သူ ဖြစ်ပါ!', + thai: 'ยังไม่มีการแปล เป็นคนแรกที่แปล!', + mandarin: '还没有翻译。成为第一个翻译者!' }, viewProjectLimitedAccess: { english: 'View Project (Limited Access)', @@ -5333,7 +8014,11 @@ export const localizations = { brazilian_portuguese: 'Ver projeto (Acesso limitado)', tok_pisin: 'Lukim Project (Limited Access)', indonesian: 'Lihat Proyek (Akses Terbatas)', - nepali: 'प्रोजेक्ट हेर्नुहोस् (सीमित पहुँच)' + nepali: 'प्रोजेक्ट हेर्नुहोस् (सीमित पहुँच)', + hindi: 'परियोजना देखें (सीमित पहुंच)', + burmese: 'စီမံကိန်းကို ကြည့်ပါ (ကန့်သတ်ချက်ဖြင့် ဝင်ရောက်ခွင့်)', + thai: 'ดูโครงการ (การเข้าถึงจำกัด)', + mandarin: '查看项目(受限访问)' }, languages: { english: 'Languages', @@ -5341,7 +8026,11 @@ export const localizations = { brazilian_portuguese: 'Idiomas', tok_pisin: 'Ol Tokples', indonesian: 'Bahasa', - nepali: 'भाषाहरू' + nepali: 'भाषाहरू', + hindi: 'भाषाएं', + burmese: 'ဘာသာစကားများ', + thai: 'ภาษา', + mandarin: '语言' }, downloadRequired: { english: 'Download required', @@ -5349,7 +8038,11 @@ export const localizations = { brazilian_portuguese: 'Download requerido', tok_pisin: 'Yu mas daunim', indonesian: 'Unduhan diperlukan', - nepali: 'डाउनलोड आवश्यक छ' + nepali: 'डाउनलोड आवश्यक छ', + hindi: 'डाउनलोड आवश्यक', + burmese: 'ဒေါင်းလုဒ်လုပ်ရန် လိုအပ်သည်', + thai: 'ต้องดาวน์โหลด', + mandarin: '需要下载' }, myProjects: { english: 'My Projects', @@ -5357,7 +8050,11 @@ export const localizations = { brazilian_portuguese: 'Meus projetos', tok_pisin: 'Ol Project Bilong Mi', indonesian: 'Proyek Saya', - nepali: 'मेरा प्रोजेक्टहरू' + nepali: 'मेरा प्रोजेक्टहरू', + hindi: 'मेरी परियोजनाएं', + burmese: 'ကျွန်ုပ်၏ စီမံကိန်းများ', + thai: 'โครงการของฉัน', + mandarin: '我的项目' }, statusTranslationActive: { english: @@ -5370,7 +8067,12 @@ export const localizations = { 'Dispela translation i active nau. Active translation i save tu.', indonesian: 'Terjemahan ini saat ini aktif. Terjemahan aktif juga terlihat.', - nepali: 'यो अनुवाद हाल सक्रिय छ। सक्रिय अनुवाद पनि दृश्यमान छ।' + nepali: 'यो अनुवाद हाल सक्रिय छ। सक्रिय अनुवाद पनि दृश्यमान छ।', + hindi: 'यह अनुवाद वर्तमान में सक्रिय है। सक्रिय अनुवाद दृश्यमान भी है।', + burmese: + 'ဤဘာသာပြန်ဆိုချက်သည် လက်ရှိတွင် အသက်ဝင်နေသည်။ အသက်ဝင်သော ဘာသာပြန်ဆိုချက်သည်လည်း မြင်နိုင်သည်။', + thai: 'การแปลนี้กำลังใช้งานอยู่ การแปลที่ใช้งานอยู่จะมองเห็นได้ด้วย', + mandarin: '此翻译当前处于活动状态。活动翻译也可见。' }, statusTranslationInactive: { english: @@ -5384,7 +8086,13 @@ export const localizations = { indonesian: 'Terjemahan ini tidak aktif. Tidak ada tindakan yang dapat dilakukan kecuali diaktifkan kembali.', nepali: - 'यो अनुवाद निष्क्रिय छ। पुन: सक्रिय नगरेसम्म कुनै कार्य गर्न सकिँदैन।' + 'यो अनुवाद निष्क्रिय छ। पुन: सक्रिय नगरेसम्म कुनै कार्य गर्न सकिँदैन।', + hindi: + 'यह अनुवाद निष्क्रिय है। पुनः सक्रिय किए बिना कोई कार्रवाई नहीं की जा सकती।', + burmese: + 'ဤဘာသာပြန်ဆိုချက်သည် အသက်မဝင်ပါ။ ပြန်လည်အသက်သွင်းမသည်အထိ မည်သည့်လုပ်ဆောင်ချက်မျှ မပြုလုပ်နိုင်ပါ။', + thai: 'การแปลนี้ไม่ใช้งาน ไม่สามารถดำเนินการใดๆ ได้เว้นแต่จะเปิดใช้งานอีกครั้ง', + mandarin: '此翻译处于非活动状态。除非重新激活,否则无法执行任何操作。' }, statusTranslationVisible: { english: 'This translation is visible to other users.', @@ -5392,7 +8100,11 @@ export const localizations = { brazilian_portuguese: 'Esta tradução está visível para outros usuários.', tok_pisin: 'Dispela translation i save long ol narapela user.', indonesian: 'Terjemahan ini terlihat oleh pengguna lain.', - nepali: 'यो अनुवाद अन्य प्रयोगकर्ताहरूलाई देखिन्छ।' + nepali: 'यो अनुवाद अन्य प्रयोगकर्ताहरूलाई देखिन्छ।', + hindi: 'यह अनुवाद अन्य उपयोगकर्ताओं को दिखाई देता है।', + burmese: 'ဤဘာသာပြန်ဆိုချက်ကို အခြားအသုံးပြုသူများ မြင်နိုင်သည်။', + thai: 'การแปลนี้มองเห็นได้โดยผู้ใช้อื่น', + mandarin: '此翻译对其他用户可见。' }, statusTranslationInvisible: { english: @@ -5406,7 +8118,13 @@ export const localizations = { indonesian: 'Terjemahan ini disembunyikan dan tidak akan ditampilkan kepada pengguna lain. Terjemahan yang tidak terlihat juga tidak aktif.', nepali: - 'यो अनुवाद लुकाइएको छ र अन्य प्रयोगकर्ताहरूलाई देखाइने छैन। अदृश्य अनुवाद पनि निष्क्रिय छ।' + 'यो अनुवाद लुकाइएको छ र अन्य प्रयोगकर्ताहरूलाई देखाइने छैन। अदृश्य अनुवाद पनि निष्क्रिय छ।', + hindi: + 'यह अनुवाद छिपा हुआ है और अन्य उपयोगकर्ताओं को नहीं दिखाया जाएगा। अदृश्य अनुवाद भी निष्क्रिय है।', + burmese: + 'ဤဘာသာပြန်ဆိုချက်ကို ဖျောက်ထားပြီး အခြားအသုံးပြုသူများကို မပြသပါ။ မမြင်ရသော ဘာသာပြန်ဆိုချက်သည်လည်း အသက်မဝင်ပါ။', + thai: 'การแปลนี้ถูกซ่อนและจะไม่แสดงให้ผู้ใช้อื่นเห็น การแปลที่มองไม่เห็นจะไม่ใช้งานด้วย', + mandarin: '此翻译已隐藏,不会向其他用户显示。不可见翻译也处于非活动状态。' }, statusTranslationMadeVisible: { english: 'The translation has been made visible', @@ -5414,7 +8132,11 @@ export const localizations = { brazilian_portuguese: 'A tradução foi tornada visível', tok_pisin: 'Translation i mekim save nau', indonesian: 'Terjemahan telah dibuat terlihat', - nepali: 'अनुवाद दृश्यमान बनाइएको छ' + nepali: 'अनुवाद दृश्यमान बनाइएको छ', + hindi: 'अनुवाद दृश्यमान बना दिया गया है', + burmese: 'ဘာသာပြန်ဆိုချက်ကို မြင်နိုင်အောင် ပြုလုပ်ပြီးပါပြီ', + thai: 'การแปลถูกทำให้มองเห็นได้', + mandarin: '翻译已设为可见' }, statusTranslationMadeInvisible: { english: 'The translation has been made invisible', @@ -5422,7 +8144,11 @@ export const localizations = { brazilian_portuguese: 'A tradução foi tornada invisível', tok_pisin: 'Translation i mekim hait nau', indonesian: 'Terjemahan telah dibuat tidak terlihat', - nepali: 'अनुवाद अदृश्य बनाइएको छ' + nepali: 'अनुवाद अदृश्य बनाइएको छ', + hindi: 'अनुवाद अदृश्य बना दिया गया है', + burmese: 'ဘာသာပြန်ဆိုချက်ကို မမြင်ရအောင် ပြုလုပ်ပြီးပါပြီ', + thai: 'การแปลถูกทำให้มองไม่เห็น', + mandarin: '翻译已设为不可见' }, statusTranslationMadeActive: { english: 'The translation has been made active', @@ -5430,7 +8156,11 @@ export const localizations = { brazilian_portuguese: 'A tradução foi ativada', tok_pisin: 'Translation i mekim active nau', indonesian: 'Terjemahan telah diaktifkan', - nepali: 'अनुवाद सक्रिय बनाइएको छ' + nepali: 'अनुवाद सक्रिय बनाइएको छ', + hindi: 'अनुवाद सक्रिय बना दिया गया है', + burmese: 'ဘာသာပြန်ဆိုချက်ကို အသက်သွင်းပြီးပါပြီ', + thai: 'การแปลถูกทำให้ใช้งาน', + mandarin: '翻译已设为活动' }, statusTranslationMadeInactive: { english: 'The translation has been made inactive', @@ -5438,7 +8168,11 @@ export const localizations = { brazilian_portuguese: 'A tradução foi desativada', tok_pisin: 'Translation i mekim stop nau', indonesian: 'Terjemahan telah dinonaktifkan', - nepali: 'अनुवाद निष्क्रिय बनाइएको छ' + nepali: 'अनुवाद निष्क्रिय बनाइएको छ', + hindi: 'अनुवाद निष्क्रिय बना दिया गया है', + burmese: 'ဘာသာပြန်ဆိုချက်ကို အသက်မဝင်အောင် ပြုလုပ်ပြီးပါပြီ', + thai: 'การแปลถูกทำให้ไม่ใช้งาน', + mandarin: '翻译已设为非活动' }, statusTranslationUpdateFailed: { english: 'Failed to update translation settings', @@ -5446,7 +8180,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao atualizar as configurações da tradução', tok_pisin: 'I no inap update translation settings', indonesian: 'Gagal mengupdate pengaturan terjemahan', - nepali: 'अनुवाद सेटिङहरू अपडेट गर्न असफल' + nepali: 'अनुवाद सेटिङहरू अपडेट गर्न असफल', + hindi: 'अनुवाद सेटिंग अपडेट करने में विफल', + burmese: 'ဘာသာပြန်ဆိုချက် ဆက်တင်များကို မွမ်းမံရန် မအောင်မြင်ပါ', + thai: 'อัปเดตการตั้งค่าการแปลไม่สำเร็จ', + mandarin: '更新翻译设置失败' }, translationSettingsLoadError: { english: 'Error loading translation settings.', @@ -5454,7 +8192,11 @@ export const localizations = { brazilian_portuguese: 'Erro ao carregar as configurações de tradução.', tok_pisin: 'I no inap load translation settings.', indonesian: 'Gagal memuat pengaturan terjemahan.', - nepali: 'अनुवाद सेटिङहरू लोड गर्दा त्रुटि।' + nepali: 'अनुवाद सेटिङहरू लोड गर्दा त्रुटि।', + hindi: 'अनुवाद सेटिंग लोड करने में त्रुटि।', + burmese: 'ဘာသာပြန်ဆိုချက် ဆက်တင်များကို လုပ်ဆောင်ရာတွင် အမှားတက်ခဲ့သည်။', + thai: 'เกิดข้อผิดพลาดในการโหลดการตั้งค่าการแปล', + mandarin: '加载翻译设置时出错。' }, contentText: { english: 'Content Text', @@ -5462,7 +8204,11 @@ export const localizations = { brazilian_portuguese: 'Texto do Conteúdo', tok_pisin: 'Content Text', indonesian: 'Teks Konten', - nepali: 'सामग्री पाठ' + nepali: 'सामग्री पाठ', + hindi: 'सामग्री पाठ', + burmese: 'အကြောင်းအရာ စာသား', + thai: 'ข้อความเนื้อหา', + mandarin: '内容文本' }, enterContentText: { english: 'Enter content text...', @@ -5470,7 +8216,11 @@ export const localizations = { brazilian_portuguese: 'Digite o texto do conteúdo...', tok_pisin: 'Putim content text...', indonesian: 'Masukkan teks konten...', - nepali: 'सामग्री पाठ प्रविष्ट गर्नुहोस्...' + nepali: 'सामग्री पाठ प्रविष्ट गर्नुहोस्...', + hindi: 'सामग्री पाठ दर्ज करें...', + burmese: 'အကြောင်းအရာ စာသားကို ထည့်သွင်းပါ...', + thai: 'ป้อนข้อความเนื้อหา...', + mandarin: '输入内容文本...' }, saving: { english: 'Saving...', @@ -5478,7 +8228,11 @@ export const localizations = { brazilian_portuguese: 'Salvando...', tok_pisin: 'Seivim...', indonesian: 'Menyimpan...', - nepali: 'सुरक्षित गर्दै...' + nepali: 'सुरक्षित गर्दै...', + hindi: 'सहेज रहा है...', + burmese: 'သိမ်းဆည်းနေသည်...', + thai: 'กำลังบันทึก...', + mandarin: '保存中...' }, localAssetEditHint: { english: 'This asset is local only. Text can be edited until published.', @@ -5490,7 +8244,14 @@ export const localizations = { 'Dispela asset i local tasol. Yu ken senisim text inap yu publishim.', indonesian: 'Aset ini hanya lokal. Teks dapat diedit hingga dipublikasikan.', - nepali: 'यो एसेट स्थानीय मात्र छ। प्रकाशित नभएसम्म पाठ सम्पादन गर्न सकिन्छ।' + nepali: + 'यो एसेट स्थानीय मात्र छ। प्रकाशित नभएसम्म पाठ सम्पादन गर्न सकिन्छ।', + hindi: + 'यह एसेट केवल स्थानीय है। प्रकाशित होने तक पाठ संपादित किया जा सकता है।', + burmese: + 'ဤပိုင်ဆိုင်မှုသည် ဒေသခံသာဖြစ်သည်။ ထုတ်ဝေမချင်းစာသားကို တည်းဖြတ်နိုင်သည်။', + thai: 'สินทรัพย์นี้เป็นแบบท้องถิ่นเท่านั้น สามารถแก้ไขข้อความได้จนกว่าจะเผยแพร่', + mandarin: '此资产仅为本地。在发布之前可以编辑文本。' }, requests: { english: 'Requests', @@ -5498,7 +8259,11 @@ export const localizations = { brazilian_portuguese: 'Solicitações', tok_pisin: 'Ol askim', indonesian: 'Permintaan', - nepali: 'अनुरोधहरू' + nepali: 'अनुरोधहरू', + hindi: 'अनुरोध', + burmese: 'တောင်းဆိုမှုများ', + thai: 'คำขอ', + mandarin: '请求' }, noPendingRequests: { english: 'No pending membership requests', @@ -5506,7 +8271,11 @@ export const localizations = { brazilian_portuguese: 'Sem solicitações de adesão pendentes', tok_pisin: 'I no gat askim i stap', indonesian: 'Tidak ada permintaan keanggotaan tertunda', - nepali: 'कुनै बाँकी सदस्यता अनुरोध छैन' + nepali: 'कुनै बाँकी सदस्यता अनुरोध छैन', + hindi: 'कोई लंबित सदस्यता अनुरोध नहीं', + burmese: 'ဆိုင်းငံ့ထားသော အဖွဲ့ဝင်တောင်းဆိုမှု မရှိပါ', + thai: 'ไม่มีคำขอเป็นสมาชิกที่รอดำเนินการ', + mandarin: '没有待处理的成员资格请求' }, confirmApprove: { english: 'Approve Request', @@ -5514,7 +8283,11 @@ export const localizations = { brazilian_portuguese: 'Aprovar Solicitação', tok_pisin: 'Orait long askim', indonesian: 'Setujui Permintaan', - nepali: 'अनुरोध स्वीकृत गर्नुहोस्' + nepali: 'अनुरोध स्वीकृत गर्नुहोस्', + hindi: 'अनुरोध स्वीकृत करें', + burmese: 'တောင်းဆိုမှုကို အတည်ပြုပါ', + thai: 'อนุมัติคำขอ', + mandarin: '批准请求' }, confirmApproveMessage: { english: 'Add {name} as a member of this project?', @@ -5522,7 +8295,11 @@ export const localizations = { brazilian_portuguese: 'Adicionar {name} como membro deste projeto?', tok_pisin: 'Putim {name} i kamap memba bilong projek?', indonesian: 'Tambahkan {name} sebagai anggota proyek ini?', - nepali: '{name} लाई यो प्रोजेक्टको सदस्यको रूपमा थप्ने?' + nepali: '{name} लाई यो प्रोजेक्टको सदस्यको रूपमा थप्ने?', + hindi: '{name} को इस परियोजना का सदस्य के रूप में जोड़ें?', + burmese: '{name} ကို ဤစီမံကိန်း၏ အဖွဲ့ဝင်အဖြစ် ထည့်မလား?', + thai: 'เพิ่ม {name} เป็นสมาชิกของโครงการนี้?', + mandarin: '将 {name} 添加为此项目的成员?' }, requestApproved: { english: 'Request approved', @@ -5530,7 +8307,11 @@ export const localizations = { brazilian_portuguese: 'Solicitação aprovada', tok_pisin: 'Askim i orait', indonesian: 'Permintaan disetujui', - nepali: 'अनुरोध स्वीकृत भयो' + nepali: 'अनुरोध स्वीकृत भयो', + hindi: 'अनुरोध स्वीकृत हो गया', + burmese: 'တောင်းဆိုမှုကို အတည်ပြုပြီးပါပြီ', + thai: 'อนุมัติคำขอแล้ว', + mandarin: '请求已批准' }, confirmDeny: { english: 'Deny Request', @@ -5538,7 +8319,11 @@ export const localizations = { brazilian_portuguese: 'Negar Solicitação', tok_pisin: 'Tambu askim', indonesian: 'Tolak Permintaan', - nepali: 'अनुरोध अस्वीकार गर्नुहोस्' + nepali: 'अनुरोध अस्वीकार गर्नुहोस्', + hindi: 'अनुरोध अस्वीकार करें', + burmese: 'တောင်းဆိုမှုကို ငြင်းပယ်ပါ', + thai: 'ปฏิเสธคำขอ', + mandarin: '拒绝请求' }, confirmDenyMessage: { english: 'Deny membership request from {name}?', @@ -5546,7 +8331,11 @@ export const localizations = { brazilian_portuguese: 'Negar solicitação de adesão de {name}?', tok_pisin: 'Tambu askim bilong {name}?', indonesian: 'Tolak permintaan keanggotaan dari {name}?', - nepali: '{name} बाट सदस्यता अनुरोध अस्वीकार गर्ने?' + nepali: '{name} बाट सदस्यता अनुरोध अस्वीकार गर्ने?', + hindi: '{name} से सदस्यता अनुरोध अस्वीकार करें?', + burmese: '{name} ထံမှ အဖွဲ့ဝင်တောင်းဆိုမှုကို ငြင်းပယ်မလား?', + thai: 'ปฏิเสธคำขอเป็นสมาชิกจาก {name}?', + mandarin: '拒绝 {name} 的成员资格请求?' }, requestDenied: { english: 'Request denied', @@ -5554,7 +8343,11 @@ export const localizations = { brazilian_portuguese: 'Solicitação negada', tok_pisin: 'Askim i tambu', indonesian: 'Permintaan ditolak', - nepali: 'अनुरोध अस्वीकृत भयो' + nepali: 'अनुरोध अस्वीकृत भयो', + hindi: 'अनुरोध अस्वीकृत हो गया', + burmese: 'တောင်းဆိုမှုကို ငြင်းပယ်ပြီးပါပြီ', + thai: 'ปฏิเสธคำขอแล้ว', + mandarin: '请求已拒绝' }, failedToApproveRequest: { english: 'Failed to approve request', @@ -5562,7 +8355,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao aprovar solicitação', tok_pisin: 'Askim i no inap orait', indonesian: 'Gagal menyetujui permintaan', - nepali: 'अनुरोध स्वीकृत गर्न असफल' + nepali: 'अनुरोध स्वीकृत गर्न असफल', + hindi: 'अनुरोध स्वीकृत करने में विफल', + burmese: 'တောင်းဆိုမှုကို အတည်ပြုရန် မအောင်မြင်ပါ', + thai: 'อนุมัติคำขอไม่สำเร็จ', + mandarin: '批准请求失败' }, failedToDenyRequest: { english: 'Failed to deny request', @@ -5570,7 +8367,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao negar solicitação', tok_pisin: 'Askim i no inap tambu', indonesian: 'Gagal menolak permintaan', - nepali: 'अनुरोध अस्वीकार गर्न असफल' + nepali: 'अनुरोध अस्वीकार गर्न असफल', + hindi: 'अनुरोध अस्वीकार करने में विफल', + burmese: 'တောင်းဆိုမှုကို ငြင်းပယ်ရန် မအောင်မြင်ပါ', + thai: 'ปฏิเสธคำขอไม่สำเร็จ', + mandarin: '拒绝请求失败' }, downloadQuestToView: { english: 'This quest must be downloaded before you can view it.', @@ -5578,7 +8379,11 @@ export const localizations = { brazilian_portuguese: 'Esta quest deve ser baixada antes de visualizá-la.', tok_pisin: 'Yu mas daunim dispela quest pastaim long lukim.', indonesian: 'Quest ini harus diunduh sebelum Anda dapat melihatnya.', - nepali: 'यो क्वेस्ट हेर्नु अघि डाउनलोड गर्नुपर्छ।' + nepali: 'यो क्वेस्ट हेर्नु अघि डाउनलोड गर्नुपर्छ।', + hindi: 'इस क्वेस्ट को देखने से पहले इसे डाउनलोड करना होगा।', + burmese: 'ဤခရီးစဉ်ကို ကြည့်ရှုမီ ဒေါင်းလုဒ်လုပ်ရပါမည်။', + thai: 'ต้องดาวน์โหลดภารกิจนี้ก่อนจึงจะดูได้', + mandarin: '在查看此任务之前必须先下载。' }, downloadNow: { english: 'Download Now', @@ -5586,7 +8391,11 @@ export const localizations = { brazilian_portuguese: 'Baixar Agora', tok_pisin: 'Daunim nau', indonesian: 'Unduh Sekarang', - nepali: 'अहिले डाउनलोड गर्नुहोस्' + nepali: 'अहिले डाउनलोड गर्नुहोस्', + hindi: 'अभी डाउनलोड करें', + burmese: 'ယခု ဒေါင်းလုဒ်လုပ်ပါ', + thai: 'ดาวน์โหลดตอนนี้', + mandarin: '立即下载' }, vadTitle: { english: 'Voice Activity', @@ -5594,7 +8403,11 @@ export const localizations = { brazilian_portuguese: 'Atividade de Voz', tok_pisin: 'Wok bilong vois', indonesian: 'Aktivitas Suara', - nepali: 'आवाज गतिविधि' + nepali: 'आवाज गतिविधि', + hindi: 'ध्वनि गतिविधि', + burmese: 'အသံ လှုပ်ရှားမှု', + thai: 'กิจกรรมเสียง', + mandarin: '语音活动' }, vadDescription: { english: 'Records automatically when you speak', @@ -5602,7 +8415,11 @@ export const localizations = { brazilian_portuguese: 'Grava automaticamente quando você fala', tok_pisin: 'Em i save record pastaim taim yu toktok', indonesian: 'Merekam otomatis saat Anda berbicara', - nepali: 'तपाईं बोल्दा स्वचालित रूपमा रेकर्ड गर्छ' + nepali: 'तपाईं बोल्दा स्वचालित रूपमा रेकर्ड गर्छ', + hindi: 'जब आप बोलते हैं तो स्वचालित रूप से रिकॉर्ड करता है', + burmese: 'သင် စကားပြောသောအခါ အလိုအလျောက် မှတ်တမ်းတင်သည်', + thai: 'บันทึกอัตโนมัติเมื่อคุณพูด', + mandarin: '说话时自动录音' }, vadCurrentLevel: { english: 'Current Level', @@ -5610,7 +8427,11 @@ export const localizations = { brazilian_portuguese: 'Nível Atual', tok_pisin: 'Level nau', indonesian: 'Level Saat Ini', - nepali: 'हालको स्तर' + nepali: 'हालको स्तर', + hindi: 'वर्तमान स्तर', + burmese: 'လက်ရှိ အဆင့်', + thai: 'ระดับปัจจุบัน', + mandarin: '当前级别' }, vadRecordingNow: { english: 'Recording', @@ -5618,7 +8439,11 @@ export const localizations = { brazilian_portuguese: 'Gravando', tok_pisin: 'I save nau', indonesian: 'Merekam', - nepali: 'रेकर्ड गर्दै' + nepali: 'रेकर्ड गर्दै', + hindi: 'रिकॉर्ड हो रहा है', + burmese: 'မှတ်တမ်းတင်နေသည်', + thai: 'กำลังบันทึก', + mandarin: '录音中' }, vadWaiting: { english: 'Waiting', @@ -5626,7 +8451,11 @@ export const localizations = { brazilian_portuguese: 'Aguardando', tok_pisin: 'Wetim', indonesian: 'Menunggu', - nepali: 'पर्खँदै' + nepali: 'पर्खँदै', + hindi: 'प्रतीक्षा कर रहा है', + burmese: 'စောင့်နေသည်', + thai: 'กำลังรอ', + mandarin: '等待中' }, vadPaused: { english: 'Paused', @@ -5634,7 +8463,11 @@ export const localizations = { brazilian_portuguese: 'Pausado', tok_pisin: 'I stop liklik', indonesian: 'Dijeda', - nepali: 'रोकिएको' + nepali: 'रोकिएको', + hindi: 'रुका हुआ', + burmese: 'ရပ်ထားသည်', + thai: 'หยุดชั่วคราว', + mandarin: '已暂停' }, vadThreshold: { english: 'Sensitivity', @@ -5642,7 +8475,11 @@ export const localizations = { brazilian_portuguese: 'Sensibilidade', tok_pisin: 'Strong bilong harim', indonesian: 'Sensitivitas', - nepali: 'संवेदनशीलता' + nepali: 'संवेदनशीलता', + hindi: 'संवेदनशीलता', + burmese: 'အာရုံခံနိုင်မှု', + thai: 'ความไว', + mandarin: '灵敏度' }, vadSilenceDuration: { english: 'Pause Length', @@ -5650,7 +8487,11 @@ export const localizations = { brazilian_portuguese: 'Duração da Pausa', tok_pisin: 'Taim bilong pas', indonesian: 'Durasi Jeda', - nepali: 'रोकाइको लम्बाइ' + nepali: 'रोकाइको लम्बाइ', + hindi: 'विराम की अवधि', + burmese: 'ရပ်နားချိန် ကြာချိန်', + thai: 'ความยาวการหยุด', + mandarin: '暂停时长' }, vadSilenceDescription: { english: 'How much silence is needed to determine segment boundaries.', @@ -5661,7 +8502,11 @@ export const localizations = { tok_pisin: 'Hamas taim i no gat nois bilong katim toktok.', indonesian: 'Berapa lama keheningan yang diperlukan untuk menentukan batas segmen.', - nepali: 'खण्ड सीमाहरू निर्धारण गर्न कति मौनता आवश्यक छ।' + nepali: 'खण्ड सीमाहरू निर्धारण गर्न कति मौनता आवश्यक छ।', + hindi: 'खंड सीमाएं निर्धारित करने के लिए कितनी चुप्पी आवश्यक है।', + burmese: 'အပိုင်းအခြားများ သတ်မှတ်ရန် ဘယ်လောက် တိတ်ဆိတ်မှု လိုအပ်သည်။', + thai: 'ต้องมีความเงียบเท่าใดเพื่อกำหนดขอบเขตของส่วน', + mandarin: '确定片段边界需要多少静音。' }, vadMinSegmentLength: { english: 'Minimum Segment Length', @@ -5669,7 +8514,11 @@ export const localizations = { brazilian_portuguese: 'Comprimento Mínimo do Segmento', tok_pisin: 'Liklik Taim Inap Bilong Toktok', indonesian: 'Panjang Segmen Minimum', - nepali: 'न्यूनतम खण्ड लम्बाइ' + nepali: 'न्यूनतम खण्ड लम्बाइ', + hindi: 'न्यूनतम खंड लंबाई', + burmese: 'အနည်းဆုံး အပိုင်း ကြာချိန်', + thai: 'ความยาวส่วนขั้นต่ำ', + mandarin: '最短片段长度' }, vadMinSegmentLengthDescription: { english: 'Discard segments below this duration (filter brief noises)', @@ -5680,7 +8529,12 @@ export const localizations = { tok_pisin: 'Rausim sotpela rekoding (filta liklik pairap)', indonesian: 'Buang segmen di bawah durasi ini (filter suara singkat)', nepali: - 'यो अवधिभन्दा कम खण्डहरू त्याग्नुहोस् (छोटो आवाजहरू फिल्टर गर्नुहोस्)' + 'यो अवधिभन्दा कम खण्डहरू त्याग्नुहोस् (छोटो आवाजहरू फिल्टर गर्नुहोस्)', + hindi: 'इस अवधि से कम खंडों को त्यागें (संक्षिप्त आवाज़ों को फ़िल्टर करें)', + burmese: + 'ဤကြာချိန်ထက် နည်းသော အပိုင်းများကို ပယ်ပါ (တိုတောင်းသော အသံများကို စစ်ထုတ်ပါ)', + thai: 'ทิ้งส่วนที่สั้นกว่าระยะเวลานี้ (กรองเสียงสั้นๆ)', + mandarin: '丢弃短于此时长的片段(过滤短暂噪音)' }, vadNoFilter: { english: 'No filter', @@ -5688,7 +8542,11 @@ export const localizations = { brazilian_portuguese: 'Sem filtro', tok_pisin: 'No filta', indonesian: 'Tanpa filter', - nepali: 'कुनै फिल्टर छैन' + nepali: 'कुनै फिल्टर छैन', + hindi: 'कोई फ़िल्टर नहीं', + burmese: 'စစ်ထုတ်မှု မရှိပါ', + thai: 'ไม่กรอง', + mandarin: '无过滤' }, vadLightFilter: { english: 'Light filter', @@ -5696,7 +8554,11 @@ export const localizations = { brazilian_portuguese: 'Filtro leve', tok_pisin: 'Liklik filta', indonesian: 'Filter ringan', - nepali: 'हल्का फिल्टर' + nepali: 'हल्का फिल्टर', + hindi: 'हल्का फ़िल्टर', + burmese: 'အပျော့စား စစ်ထုတ်မှု', + thai: 'กรองเบา', + mandarin: '轻度过滤' }, vadMediumFilter: { english: 'Medium filter', @@ -5704,7 +8566,11 @@ export const localizations = { brazilian_portuguese: 'Filtro médio', tok_pisin: 'Namel filta', indonesian: 'Filter sedang', - nepali: 'मध्यम फिल्टर' + nepali: 'मध्यम फिल्टर', + hindi: 'मध्यम फ़िल्टर', + burmese: 'အလယ်အလတ် စစ်ထုတ်မှု', + thai: 'กรองปานกลาง', + mandarin: '中度过滤' }, vadStrongFilter: { english: 'Strong filter', @@ -5712,7 +8578,11 @@ export const localizations = { brazilian_portuguese: 'Filtro forte', tok_pisin: 'Strongpela filta', indonesian: 'Filter kuat', - nepali: 'बलियो फिल्टर' + nepali: 'बलियो फिल्टर', + hindi: 'मजबूत फ़िल्टर', + burmese: 'ပြင်းထန်သော စစ်ထုတ်မှု', + thai: 'กรองแรง', + mandarin: '强力过滤' }, vadSensitive: { english: 'Sensitive', @@ -5720,7 +8590,11 @@ export const localizations = { brazilian_portuguese: 'Sensível', tok_pisin: 'I harim gut', indonesian: 'Sensitif', - nepali: 'संवेदनशील' + nepali: 'संवेदनशील', + hindi: 'संवेदनशील', + burmese: 'အာရုံခံနိုင်သည်', + thai: 'ไว', + mandarin: '敏感' }, vadNormal: { english: 'Normal', @@ -5728,7 +8602,11 @@ export const localizations = { brazilian_portuguese: 'Normal', tok_pisin: 'Nambawan', indonesian: 'Normal', - nepali: 'सामान्य' + nepali: 'सामान्य', + hindi: 'सामान्य', + burmese: 'ပုံမှန်', + thai: 'ปกติ', + mandarin: '普通' }, vadLoud: { english: 'Loud', @@ -5736,7 +8614,11 @@ export const localizations = { brazilian_portuguese: 'Alto', tok_pisin: 'Bikpela nois', indonesian: 'Keras', - nepali: 'चर्को' + nepali: 'चर्को', + hindi: 'तेज़', + burmese: 'ကျယ်လောင်သည်', + thai: 'ดัง', + mandarin: '响亮' }, vadVerySensitive: { english: 'Very Sensitive', @@ -5744,7 +8626,11 @@ export const localizations = { brazilian_portuguese: 'Muito Sensível', tok_pisin: 'I harim tumas', indonesian: 'Sangat Sensitif', - nepali: 'अत्यन्त संवेदनशील' + nepali: 'अत्यन्त संवेदनशील', + hindi: 'बहुत संवेदनशील', + burmese: 'အလွန်အာရုံခံနိုင်သည်', + thai: 'ไวมาก', + mandarin: '非常敏感' }, vadLoudOnly: { english: 'Loud Only', @@ -5752,7 +8638,11 @@ export const localizations = { brazilian_portuguese: 'Apenas Alto', tok_pisin: 'Bikpela nois tasol', indonesian: 'Keras Saja', - nepali: 'चर्को मात्र' + nepali: 'चर्को मात्र', + hindi: 'केवल तेज़', + burmese: 'ကျယ်လောင်သောအသံသာ', + thai: 'ดังเท่านั้น', + mandarin: '仅响亮' }, vadVeryLoud: { english: 'Very Loud', @@ -5760,7 +8650,11 @@ export const localizations = { brazilian_portuguese: 'Muito Alto', tok_pisin: 'Bikpela nois tumas', indonesian: 'Sangat Keras', - nepali: 'अत्यन्त चर्को' + nepali: 'अत्यन्त चर्को', + hindi: 'बहुत तेज़', + burmese: 'အလွန်ကျယ်လောင်သည်', + thai: 'ดังมาก', + mandarin: '非常响亮' }, vadQuickSegments: { english: 'Quick', @@ -5768,7 +8662,11 @@ export const localizations = { brazilian_portuguese: 'Rápido', tok_pisin: 'Kwik', indonesian: 'Cepat', - nepali: 'छिटो' + nepali: 'छिटो', + hindi: 'तेज़', + burmese: 'မြန်သည်', + thai: 'เร็ว', + mandarin: '快速' }, vadBalanced: { english: 'Balanced', @@ -5776,7 +8674,11 @@ export const localizations = { brazilian_portuguese: 'Equilibrado', tok_pisin: 'Naispela', indonesian: 'Seimbang', - nepali: 'सन्तुलित' + nepali: 'सन्तुलित', + hindi: 'संतुलित', + burmese: 'ချိန်ခွင်လျှာ', + thai: 'สมดุล', + mandarin: '平衡' }, vadCompleteThoughts: { english: 'Complete', @@ -5784,7 +8686,11 @@ export const localizations = { brazilian_portuguese: 'Completo', tok_pisin: 'Olgeta', indonesian: 'Lengkap', - nepali: 'पूर्ण' + nepali: 'पूर्ण', + hindi: 'पूर्ण', + burmese: 'ပြည့်စုံသည်', + thai: 'สมบูรณ์', + mandarin: '完整' }, vadDisplayMode: { english: 'Display Mode', @@ -5792,7 +8698,11 @@ export const localizations = { brazilian_portuguese: 'Modo de Exibição', tok_pisin: 'Kaim bilong lukim', indonesian: 'Mode Tampilan', - nepali: 'प्रदर्शन मोड' + nepali: 'प्रदर्शन मोड', + hindi: 'प्रदर्शन मोड', + burmese: 'ပြသမှု မုဒ်', + thai: 'โหมดแสดงผล', + mandarin: '显示模式' }, vadFullScreen: { english: 'Full Screen', @@ -5800,7 +8710,11 @@ export const localizations = { brazilian_portuguese: 'Tela Cheia', tok_pisin: 'Fulap skrin', indonesian: 'Layar Penuh', - nepali: 'पूर्ण स्क्रिन' + nepali: 'पूर्ण स्क्रिन', + hindi: 'पूर्ण स्क्रीन', + burmese: 'ဖန်သားပြင် အပြည့်', + thai: 'เต็มจอ', + mandarin: '全屏' }, vadFooter: { english: 'Footer', @@ -5808,7 +8722,11 @@ export const localizations = { brazilian_portuguese: 'Rodapé', tok_pisin: 'Asdaun', indonesian: 'Footer', - nepali: 'फुटर' + nepali: 'फुटर', + hindi: 'फुटर', + burmese: 'အောက်ခြေ', + thai: 'ส่วนท้าย', + mandarin: '页脚' }, vadDisplayDescription: { english: 'Choose how the waveform appears when recording', @@ -5816,7 +8734,11 @@ export const localizations = { brazilian_portuguese: 'Escolha como a forma de onda aparece ao gravar', tok_pisin: 'Makim olsem wanem wevpom i kamap taim yu save record', indonesian: 'Pilih bagaimana bentuk gelombang muncul saat merekam', - nepali: 'रेकर्डिङ गर्दा तरंग कसरी देखा पर्छ छान्नुहोस्' + nepali: 'रेकर्डिङ गर्दा तरंग कसरी देखा पर्छ छान्नुहोस्', + hindi: 'रिकॉर्डिंग करते समय तरंग कैसे दिखे यह चुनें', + burmese: 'မှတ်တမ်းတင်နေစဉ် လှိုင်းပုံစံ မည်သို့ပေါ်မည်ကို ရွေးပါ', + thai: 'เลือกวิธีแสดงคลื่นเสียงเมื่อบันทึก', + mandarin: '选择录音时波形的显示方式' }, vadStop: { english: 'Stop Recording', @@ -5824,7 +8746,11 @@ export const localizations = { brazilian_portuguese: 'Parar Gravação', tok_pisin: 'Stopim rekod', indonesian: 'Berhenti Merekam', - nepali: 'रेकर्डिङ रोक्नुहोस्' + nepali: 'रेकर्डिङ रोक्नुहोस्', + hindi: 'रिकॉर्डिंग रोकें', + burmese: 'မှတ်တမ်းတင်ခြင်း ရပ်ပါ', + thai: 'หยุดการบันทึก', + mandarin: '停止录音' }, vadHelpTitle: { english: 'How It Works', @@ -5832,7 +8758,11 @@ export const localizations = { brazilian_portuguese: 'Como Funciona', tok_pisin: 'Olsem wanem em i wok', indonesian: 'Cara Kerja', - nepali: 'यो कसरी काम गर्छ' + nepali: 'यो कसरी काम गर्छ', + hindi: 'यह कैसे काम करता है', + burmese: 'အလုပ်လုပ်ပုံ', + thai: 'วิธีการทำงาน', + mandarin: '工作原理' }, vadHelpAutomatic: { english: @@ -5846,7 +8776,14 @@ export const localizations = { indonesian: 'Saat suara terdeteksi, segmen akan mulai merekam secara otomatis. Setelah keheningan, segmen akan disimpan. Anda dapat merekam beberapa segmen seperti ini secara berurutan saat perekaman diaktifkan.', nepali: - 'जब आवाज पत्ता लाग्छ एक खण्ड स्वचालित रूपमा रेकर्डिङ सुरु हुनेछ। केही मौनता पछि खण्ड सेभ हुनेछ। रेकर्डिङ सक्रिय हुँदा तपाईं यसरी क्रमशः धेरै खण्डहरू रेकर्ड गर्न सक्नुहुन्छ।' + 'जब आवाज पत्ता लाग्छ एक खण्ड स्वचालित रूपमा रेकर्डिङ सुरु हुनेछ। केही मौनता पछि खण्ड सेभ हुनेछ। रेकर्डिङ सक्रिय हुँदा तपाईं यसरी क्रमशः धेरै खण्डहरू रेकर्ड गर्न सक्नुहुन्छ।', + hindi: + 'जब ध्वनि का पता चलता है तो एक सेगमेंट स्वचालित रूप से रिकॉर्डिंग शुरू होगा। कुछ चुप्पी के बाद सेगमेंट सहेजा जाएगा। रिकॉर्डिंग सक्रिय होने पर आप इस तरह क्रम में कई सेगमेंट रिकॉर्ड कर सकते हैं।', + burmese: + 'အသံ ခံယူမိသောအခါ အပိုင်းတစ်ခုသည် အလိုအလျောက် မှတ်တမ်းတင်မည်။ တိတ်ဆိတ်မှု အနည်းငယ်ပြီးနောက် အပိုင်းကို သိမ်းဆည်းမည်။ မှတ်တမ်းတင်ခြင်း ဖွင့်ထားစဉ် ဤကဲ့သို့ အစဉ်လိုက် အပိုင်းများစွာ မှတ်တမ်းတင်နိုင်ပါသည်။', + thai: 'เมื่อตรวจพบเสียง ส่วนจะเริ่มบันทึกโดยอัตโนมัติ หลังจากความเงียบสักครู่ ส่วนจะถูกบันทึก คุณสามารถบันทึกหลายส่วนแบบนี้ตามลำดับขณะเปิดการบันทึก', + mandarin: + '检测到声音时,片段将自动开始录音。静音一段时间后,片段将被保存。录音激活时,您可以按顺序这样录制多个片段。' }, vadHelpSensitivity: { english: @@ -5860,7 +8797,14 @@ export const localizations = { indonesian: 'Sensitivitas mengatur ambang batas untuk menentukan kapan klip dimulai dan berakhir. Sensitivitas rendah menangkap suara pelan, tetapi juga suara lain yang mungkin.', nepali: - 'संवेदनशीलताले क्लिप कहिले सुरु र समाप्त हुन्छ निर्धारण गर्न सीमा सेट गर्छ। कम संवेदनशीलताले शान्त बोली समात्छ, तर अन्य सम्भावित आवाजहरू पनि।' + 'संवेदनशीलताले क्लिप कहिले सुरु र समाप्त हुन्छ निर्धारण गर्न सीमा सेट गर्छ। कम संवेदनशीलताले शान्त बोली समात्छ, तर अन्य सम्भावित आवाजहरू पनि।', + hindi: + 'संवेदनशीलता क्लिप कब शुरू और समाप्त होता है यह निर्धारित करने के लिए सीमा सेट करती है। कम संवेदनशीलता शांत भाषण पकड़ती है, लेकिन अन्य संभावित आवाज़ें भी।', + burmese: + 'အာရုံခံနိုင်မှုသည် ကလစ်က မည်သည့်အခါ စပြီး ပြီးဆုံးသည်ကို သတ်မှတ်ရန် အနည်းဆုံး အဆင့်ကို သတ်မှတ်သည်။ အာရုံခံနိုင်မှု နည်းလျှင် အသံငယ်ငယ် စကားပြောချက်ကို ဖမ်းယူသည်၊ သို့သော် အခြား အသံများကိုလည်း ဖမ်းနိုင်သည်။', + thai: 'ความไวกำหนดเกณฑ์เพื่อกำหนดว่าเมื่อไหร่คลิปจะเริ่มและจบ ความไวต่ำจะจับเสียงพูดที่เบา แต่ก็อาจจับเสียงอื่นๆ ด้วย', + mandarin: + '灵敏度设置用于确定片段何时开始和结束的阈值。较低的灵敏度可捕捉较安静的语音,但也会捕捉其他潜在噪音。' }, vadHelpPause: { english: @@ -5874,7 +8818,13 @@ export const localizations = { indonesian: 'Durasi jeda yang lebih pendek akan memecah rekaman Anda menjadi lebih banyak segmen pada jeda yang lebih kecil.', nepali: - 'छोटो रोकाइको लम्बाइले तपाईंको रेकर्डिङलाई साना रोकाइहरूमा धेरै खण्डहरूमा विभाजन गर्नेछ।' + 'छोटो रोकाइको लम्बाइले तपाईंको रेकर्डिङलाई साना रोकाइहरूमा धेरै खण्डहरूमा विभाजन गर्नेछ।', + hindi: + 'छोटी विराम लंबाई आपकी रिकॉर्डिंग को छोटे विरामों में अधिक खंडों में विभाजित करेगी।', + burmese: + 'ရပ်နားချိန် ပိုတိုလျှင် သင်၏ မှတ်တမ်းကို ရပ်နားချိန် ကြာချိန် ပိုတိုသောနေရာများတွင် အပိုင်းများစွာ ခွဲပါမည်။', + thai: 'ความยาวการหยุดที่สั้นลงจะแบ่งการบันทึกของคุณเป็นส่วนมากขึ้นที่จุดหยุดที่สั้นลง', + mandarin: '较短的暂停时长会在较小的停顿处将您的录音分成更多片段。' }, vadHelpMinSegment: { english: @@ -5888,7 +8838,13 @@ export const localizations = { indonesian: 'Panjang Segmen Minimum mencegah penyimpanan segmen yang sangat pendek di bawah durasi yang ditetapkan, seperti batuk atau bunyi pintu.', nepali: - 'न्यूनतम खण्ड लम्बाइले सेट गरिएको अवधिभन्दा कम धेरै छोटो खण्डहरू सेभ गर्नबाट रोक्छ, जस्तै खोकी वा ढोका ठोक्ने आवाज।' + 'न्यूनतम खण्ड लम्बाइले सेट गरिएको अवधिभन्दा कम धेरै छोटो खण्डहरू सेभ गर्नबाट रोक्छ, जस्तै खोकी वा ढोका ठोक्ने आवाज।', + hindi: + 'न्यूनतम खंड लंबाई सेट अवधि से कम बहुत छोटे खंडों को सहेजने से रोकती है, जैसे खांसी या दरवाजा बंद करने की आवाज।', + burmese: + 'အနည်းဆုံး အပိုင်း ကြာချိန်သည် သတ်မှတ်ထားသော ကြာချိန်ထက် နည်းသော အလွန်တိုသော အပိုင်းများကို သိမ်းဆည်းခြင်းမှ ကာကွယ်သည်၊ ဥပမာ ချောင်းဆိုးခြင်း သို့မဟုတ် တံခါးပိတ်သံ။', + thai: 'ความยาวส่วนขั้นต่ำป้องกันการบันทึกส่วนที่สั้นมากกว่ากำหนด เช่น เสียงไอหรือเสียงประตูปิด', + mandarin: '最短片段长度可防止保存短于设定时长的片段,例如咳嗽或关门声。' }, vadRecordingSettings: { english: 'VAD recording settings', @@ -5896,7 +8852,11 @@ export const localizations = { brazilian_portuguese: 'Configurações de gravação VAD', tok_pisin: 'VAD rekoding seting', indonesian: 'Pengaturan perekaman VAD', - nepali: 'VAD रेकर्डिङ सेटिङहरू' + nepali: 'VAD रेकर्डिङ सेटिङहरू', + hindi: 'VAD रिकॉर्डिंग सेटिंग', + burmese: 'VAD မှတ်တမ်းတင်ခြင်း ဆက်တင်များ', + thai: 'การตั้งค่าการบันทึก VAD', + mandarin: 'VAD 录音设置' }, startRecording: { english: 'Record', @@ -5904,7 +8864,11 @@ export const localizations = { brazilian_portuguese: 'Gravar', tok_pisin: 'Rekodim', indonesian: 'Rekam', - nepali: 'रेकर्ड' + nepali: 'रेकर्ड', + hindi: 'रिकॉर्ड', + burmese: 'မှတ်တမ်းတင်ပါ', + thai: 'บันทึก', + mandarin: '录音' }, stopRecording: { english: 'Stop Recording', @@ -5912,7 +8876,11 @@ export const localizations = { brazilian_portuguese: 'Parar Gravação', tok_pisin: 'Stopim Rekodim', indonesian: 'Hentikan Perekaman', - nepali: 'रेकर्डिङ रोक्नुहोस्' + nepali: 'रेकर्डिङ रोक्नुहोस्', + hindi: 'रिकॉर्डिंग रोकें', + burmese: 'မှတ်တမ်းတင်ခြင်း ရပ်ပါ', + thai: 'หยุดการบันทึก', + mandarin: '停止录音' }, vadRecordingActive: { english: 'VAD recording active', @@ -5920,7 +8888,11 @@ export const localizations = { brazilian_portuguese: 'Gravação VAD ativa', tok_pisin: 'VAD rekoding i wok', indonesian: 'Perekaman VAD aktif', - nepali: 'VAD रेकर्डिङ सक्रिय' + nepali: 'VAD रेकर्डिङ सक्रिय', + hindi: 'VAD रिकॉर्डिंग सक्रिय', + burmese: 'VAD မှတ်တမ်းတင်ခြင်း အသက်ဝင်နေသည်', + thai: 'การบันทึก VAD กำลังทำงาน', + mandarin: 'VAD 录音已激活' }, recordingHelpTitle: { english: 'Two ways to record', @@ -5928,7 +8900,11 @@ export const localizations = { brazilian_portuguese: 'Duas formas de gravar', tok_pisin: 'Tupela rot bilong rekodim', indonesian: 'Dua cara merekam', - nepali: 'रेकर्ड गर्ने दुई तरिका' + nepali: 'रेकर्ड गर्ने दुई तरिका', + hindi: 'रिकॉर्ड करने के दो तरीके', + burmese: 'မှတ်တမ်းတင်ရန် နည်းလမ်း နှစ်ခု', + thai: 'สองวิธีในการบันทึก', + mandarin: '两种录制方式' }, recordingHelpVAD: { english: @@ -5942,7 +8918,14 @@ export const localizations = { indonesian: 'tombol rekam untuk memulai perekaman Voice Activity Detection (VAD). Ini akan secara otomatis membuat segmen audio baru setiap kali Anda berhenti sejenak saat berbicara. Ketuk lagi untuk mengakhiri sesi VAD.', nepali: - 'भ्वाइस एक्टिभिटी डिटेक्शन (VAD) रेकर्डिङ सुरु गर्न रेकर्ड बटन थिच्नुहोस्। यसले तपाईंले बोल्दा रोक्दा स्वचालित रूपमा नयाँ अडियो खण्डहरू सिर्जना गर्नेछ। VAD सत्र समाप्त गर्न फेरि ट्याप गर्नुहोस्।' + 'भ्वाइस एक्टिभिटी डिटेक्शन (VAD) रेकर्डिङ सुरु गर्न रेकर्ड बटन थिच्नुहोस्। यसले तपाईंले बोल्दा रोक्दा स्वचालित रूपमा नयाँ अडियो खण्डहरू सिर्जना गर्नेछ। VAD सत्र समाप्त गर्न फेरि ट्याप गर्नुहोस्।', + hindi: + 'वॉइस एक्टिविटी डिटेक्शन (VAD) रिकॉर्डिंग शुरू करने के लिए रिकॉर्ड बटन दबाएं। यह आपके बोलते समय रुकने पर स्वचालित रूप से नए ऑडियो सेगमेंट बनाएगा। VAD सत्र समाप्त करने के लिए फिर से टैप करें।', + burmese: + 'အသံလှုပ်ရှားမှု ထောက်လှမ်းခြင်း (VAD) မှတ်တမ်းတင်ရန် မှတ်တမ်းတင်ခလုတ်ကို နှိပ်ပါ။ သင်စကားပြောနေစဉ် ရပ်နားသောအခါ ဤသည် အသံအပိုင်းအစအသစ်များကို အလိုအလျောက် ဖန်တီးပေးမည်။ VAD အစည်းအဝေးကို အဆုံးသတ်ရန် ထပ်မံနှိပ်ပါ။', + thai: 'ปุ่มบันทึกเพื่อเริ่มการบันทึกด้วยการตรวจจับกิจกรรมเสียง (VAD) ซึ่งจะสร้างส่วนเสียงใหม่โดยอัตโนมัติเมื่อคุณหยุดพูดขณะพูด กดอีกครั้งเพื่อสิ้นสุดเซสชัน VAD', + mandarin: + '录音按钮以开始语音活动检测 (VAD) 录音。当您说话时暂停,这将自动创建新的音频片段。再次点击以结束 VAD 会话。' }, recordingHelpPushToTalk: { english: 'to record a single segment, release to stop recording.', @@ -5952,7 +8935,12 @@ export const localizations = { tok_pisin: 'bilong rekodim wanpela hap, lusim bilong stopim rekoding.', indonesian: 'untuk merekam satu segmen, lepaskan untuk menghentikan perekaman.', - nepali: 'एउटा खण्ड रेकर्ड गर्न, रेकर्डिङ रोक्न छोड्नुहोस्।' + nepali: 'एउटा खण्ड रेकर्ड गर्न, रेकर्डिङ रोक्न छोड्नुहोस्।', + hindi: 'एकल सेगमेंट रिकॉर्ड करने के लिए, रिकॉर्डिंग रोकने के लिए छोड़ें।', + burmese: + 'အပိုင်းအစတစ်ခုကို မှတ်တမ်းတင်ရန်၊ မှတ်တမ်းတင်ခြင်းကို ရပ်တန့်ရန် လွှတ်ပါ။', + thai: 'เพื่อบันทึกส่วนเดียว ปล่อยเพื่อหยุดการบันทึก', + mandarin: '录制单个片段,松开以停止录制。' }, tap: { english: 'Tap', @@ -5960,7 +8948,11 @@ export const localizations = { brazilian_portuguese: 'Toque', tok_pisin: 'Paitim', indonesian: 'Ketuk', - nepali: 'ट्याप गर्नुहोस्' + nepali: 'ट्याप गर्नुहोस्', + hindi: 'टैप करें', + burmese: 'နှိပ်ပါ', + thai: 'แตะ', + mandarin: '点击' }, pressAndHold: { english: 'Press and hold', @@ -5968,7 +8960,11 @@ export const localizations = { brazilian_portuguese: 'Pressione e segure', tok_pisin: 'Presim na holim', indonesian: 'Tekan dan tahan', - nepali: 'थिच्नुहोस् र होल्ड गर्नुहोस्' + nepali: 'थिच्नुहोस् र होल्ड गर्नुहोस्', + hindi: 'दबाएं और पकड़ें', + burmese: 'နှိပ်ပြီး ကိုင်ထားပါ', + thai: 'กดค้างไว้', + mandarin: '按住' }, vadAutoCalibrate: { english: 'Auto-Calibrate', @@ -5976,7 +8972,11 @@ export const localizations = { brazilian_portuguese: 'Auto-Calibrar', tok_pisin: 'Olsem wanem yet', indonesian: 'Auto-Kalibrasi', - nepali: 'स्वत: क्यालिब्रेट' + nepali: 'स्वत: क्यालिब्रेट', + hindi: 'स्वतः कैलिब्रेट', + burmese: 'အလိုအလျောက် ချိန်ညှိခြင်း', + thai: 'ปรับเทียบอัตโนมัติ', + mandarin: '自动校准' }, vadCalibrating: { english: 'Calibrating...', @@ -5984,7 +8984,11 @@ export const localizations = { brazilian_portuguese: 'Calibrando...', tok_pisin: 'Wokim nau...', indonesian: 'Mengkalibrasi...', - nepali: 'क्यालिब्रेट गर्दै...' + nepali: 'क्यालिब्रेट गर्दै...', + hindi: 'कैलिब्रेट हो रहा है...', + burmese: 'ချိန်ညှိနေသည်...', + thai: 'กำลังปรับเทียบ...', + mandarin: '校准中...' }, vadCalibrationFailed: { english: 'Calibration failed. Please try again in a quieter environment.', @@ -5996,7 +9000,12 @@ export const localizations = { indonesian: 'Kalibrasi gagal. Silakan coba lagi di lingkungan yang lebih tenang.', nepali: - 'क्यालिब्रेसन असफल भयो। कृपया शान्त वातावरणमा पुन: प्रयास गर्नुहोस्।' + 'क्यालिब्रेसन असफल भयो। कृपया शान्त वातावरणमा पुन: प्रयास गर्नुहोस्।', + hindi: 'कैलिब्रेशन विफल। कृपया शांत वातावरण में पुनः प्रयास करें।', + burmese: + 'ချိန်ညှိခြင်း မအောင်မြင်ပါ။ ကျေးဇူးပြု၍ ပိုတိတ်ဆိတ်သော ပတ်ဝန်းကျင်တွင် ထပ်မံကြိုးစားပါ။', + thai: 'การปรับเทียบล้มเหลว กรุณาลองอีกครั้งในสภาพแวดล้อมที่เงียบกว่า', + mandarin: '校准失败。请在更安静的环境中重试。' }, vadCalibrateHint: { english: @@ -6010,7 +9019,13 @@ export const localizations = { indonesian: 'Selama kalibrasi otomatis tetaplah diam atau hanya izinkan suara yang ingin Anda agar berada di bawah ambang sensitivitas.', nepali: - 'स्वत: क्यालिब्रेसनको समयमा मौन रहनुहोस् वा केवल ती आवाजहरू मात्र अनुमति दिनुहोस् जुन तपाईं संवेदनशीलता सीमाभन्दा तल चाहनुहुन्छ।' + 'स्वत: क्यालिब्रेसनको समयमा मौन रहनुहोस् वा केवल ती आवाजहरू मात्र अनुमति दिनुहोस् जुन तपाईं संवेदनशीलता सीमाभन्दा तल चाहनुहुन्छ।', + hindi: + 'स्वतः कैलिब्रेशन के दौरान चुप रहें या केवल उन आवाज़ों की अनुमति दें जिन्हें आप संवेदनशीलता सीमा से नीचे चाहते हैं।', + burmese: + 'အလိုအလျောက် ချိန်ညှိချိန်တွင် တိတ်ဆိတ်စွာ နေပါ သို့မဟုတ် အာရုံခံနိုင်မှု အနည်းဆုံးအဆင့်အောက်တွင် ရှိစေလိုသော အသံများကိုသာ ခွင့်ပြုပါ။', + thai: 'ระหว่างการปรับเทียบอัตโนมัติ อยู่เงียบๆ หรืออนุญาตเฉพาะเสียงที่คุณต้องการให้อยู่ต่ำกว่าเกณฑ์ความไว', + mandarin: '自动校准期间请保持安静,或仅允许您希望低于灵敏度阈值的声音。' }, appUpgradeRequired: { english: 'App Upgrade Required', @@ -6018,7 +9033,11 @@ export const localizations = { brazilian_portuguese: 'Atualização do App Necessária', tok_pisin: 'Yu mas upgreidim app', indonesian: 'Pembaruan Aplikasi Diperlukan', - nepali: 'एप अपग्रेड आवश्यक छ' + nepali: 'एप अपग्रेड आवश्यक छ', + hindi: 'ऐप अपग्रेड आवश्यक है', + burmese: 'အက်ပ်ကို အဆင့်မြှင့်တင်ရန် လိုအပ်သည်', + thai: 'ต้องอัปเกรดแอป', + mandarin: '需要升级应用' }, appUpgradeServerAhead: { english: @@ -6032,7 +9051,13 @@ export const localizations = { indonesian: 'Versi baru aplikasi diperlukan untuk mengakses fitur terbaru. Silakan perbarui untuk melanjutkan.', nepali: - 'नवीनतम सुविधाहरू पहुँच गर्न एपको नयाँ संस्करण आवश्यक छ। कृपया जारी राख्न अपडेट गर्नुहोस्।' + 'नवीनतम सुविधाहरू पहुँच गर्न एपको नयाँ संस्करण आवश्यक छ। कृपया जारी राख्न अपडेट गर्नुहोस्।', + hindi: + 'नवीनतम सुविधाओं तक पहुंचने के लिए ऐप का नया संस्करण आवश्यक है। कृपया जारी रखने के लिए अपडेट करें।', + burmese: + 'နောက်ဆုံးပေါ် အင်္ဂါရပ်များကို အသုံးပြုရန် အက်ပ်၏ ဗားရှင်းအသစ် လိုအပ်သည်။ ကျေးဇူးပြု၍ ဆက်လက်လုပ်ဆောင်ရန် အပ်ဒိတ်လုပ်ပါ။', + thai: 'ต้องใช้เวอร์ชันใหม่ของแอปเพื่อเข้าถึงฟีเจอร์ล่าสุด กรุณาอัปเดตเพื่อดำเนินการต่อ', + mandarin: '需要应用的新版本才能访问最新功能。请更新以继续。' }, appUpgradeServerBehind: { english: @@ -6046,7 +9071,13 @@ export const localizations = { indonesian: 'Versi aplikasi Anda lebih baru dari server. Silakan hubungi dukungan atau tunggu server diperbarui.', nepali: - 'तपाईंको एप संस्करण सर्भरभन्दा नयाँ छ। कृपया समर्थनलाई सम्पर्क गर्नुहोस् वा सर्भर अपडेट हुने प्रतीक्षा गर्नुहोस्।' + 'तपाईंको एप संस्करण सर्भरभन्दा नयाँ छ। कृपया समर्थनलाई सम्पर्क गर्नुहोस् वा सर्भर अपडेट हुने प्रतीक्षा गर्नुहोस्।', + hindi: + 'आपका ऐप संस्करण सर्वर से नया है। कृपया सहायता से संपर्क करें या सर्वर अपडेट होने की प्रतीक्षा करें।', + burmese: + 'သင်၏ အက်ပ်ဗားရှင်းသည် ဆာဗာထက် ပိုမိုသစ်သည်။ ကျေးဇူးပြု၍ အကူအညီကို ဆက်သွယ်ပါ သို့မဟုတ် ဆာဗာ အပ်ဒိတ်လုပ်ရန် စောင့်ပါ။', + thai: 'เวอร์ชันแอปของคุณใหม่กว่าซีร์เวอร์ กรุณาติดต่อฝ่ายสนับสนุนหรือรอให้ซีร์เวอร์อัปเดต', + mandarin: '您的应用版本比服务器新。请联系支持或等待服务器更新。' }, upgradeToVersion: { english: 'Please upgrade to version {version}', @@ -6054,7 +9085,11 @@ export const localizations = { brazilian_portuguese: 'Por favor atualize para a versão {version}', tok_pisin: 'Plis upgreidim long version {version}', indonesian: 'Silakan perbarui ke versi {version}', - nepali: 'कृपया संस्करण {version} मा अपग्रेड गर्नुहोस्' + nepali: 'कृपया संस्करण {version} मा अपग्रेड गर्नुहोस्', + hindi: 'कृपया संस्करण {version} में अपग्रेड करें', + burmese: 'ကျေးဇူးပြု၍ ဗားရှင်း {version} သို့ အဆင့်မြှင့်တင်ပါ', + thai: 'กรุณาอัปเกรดเป็นเวอร์ชัน {version}', + mandarin: '请升级到版本 {version}' }, currentVersion: { english: 'Current Version', @@ -6062,7 +9097,11 @@ export const localizations = { brazilian_portuguese: 'Versão Atual', tok_pisin: 'Version nau', indonesian: 'Versi Saat Ini', - nepali: 'हालको संस्करण' + nepali: 'हालको संस्करण', + hindi: 'वर्तमान संस्करण', + burmese: 'လက်ရှိ ဗားရှင်း', + thai: 'เวอร์ชันปัจจุบัน', + mandarin: '当前版本' }, requiredVersion: { english: 'Required Version', @@ -6070,7 +9109,11 @@ export const localizations = { brazilian_portuguese: 'Versão Necessária', tok_pisin: 'Version yu mas gat', indonesian: 'Versi yang Diperlukan', - nepali: 'आवश्यक संस्करण' + nepali: 'आवश्यक संस्करण', + hindi: 'आवश्यक संस्करण', + burmese: 'လိုအပ်သော ဗားရှင်း', + thai: 'เวอร์ชันที่ต้องการ', + mandarin: '所需版本' }, upgradeApp: { english: 'Upgrade App', @@ -6078,7 +9121,11 @@ export const localizations = { brazilian_portuguese: 'Atualizar App', tok_pisin: 'Upgreidim App', indonesian: 'Perbarui Aplikasi', - nepali: 'एप अपग्रेड गर्नुहोस्' + nepali: 'एप अपग्रेड गर्नुहोस्', + hindi: 'ऐप अपग्रेड करें', + burmese: 'အက်ပ်ကို အဆင့်မြှင့်တင်ပါ', + thai: 'อัปเกรดแอป', + mandarin: '升级应用' }, checkingSchemaVersion: { english: 'Checking schema compatibility...', @@ -6086,7 +9133,11 @@ export const localizations = { brazilian_portuguese: 'Verificando compatibilidade do esquema...', tok_pisin: 'Checkim schema compatibility...', indonesian: 'Memeriksa kompatibilitas skema...', - nepali: 'स्किमा अनुकूलता जाँच गर्दै...' + nepali: 'स्किमा अनुकूलता जाँच गर्दै...', + hindi: 'स्कीमा अनुकूलता जांच रहे हैं...', + burmese: 'စံညွှန်း ကိုက်ညီမှုကို စစ်ဆေးနေသည်...', + thai: 'กำลังตรวจสอบความเข้ากันได้ของสคีมา...', + mandarin: '正在检查架构兼容性...' }, scanningCorruptedAttachments: { english: 'Scanning for corrupted attachments...', @@ -6094,7 +9145,11 @@ export const localizations = { brazilian_portuguese: 'Procurando anexos corrompidos...', tok_pisin: 'Lukluk long ol bagarap fayl...', indonesian: 'Memindai lampiran yang rusak...', - nepali: 'बिग्रिएका संलग्नकहरू स्क्यान गर्दै...' + nepali: 'बिग्रिएका संलग्नकहरू स्क्यान गर्दै...', + hindi: 'दूषित संलग्नकों को स्कैन कर रहे हैं...', + burmese: 'ပျက်စီးသော ပူးတွဲဖိုင်များကို စကင်န်ဖတ်နေသည်...', + thai: 'กำลังสแกนหาไฟล์แนบที่เสียหาย...', + mandarin: '正在扫描损坏的附件...' }, noCorruptedAttachments: { english: 'No Corrupted Attachments', @@ -6102,7 +9157,11 @@ export const localizations = { brazilian_portuguese: 'Sem Anexos Corrompidos', tok_pisin: 'I no gat bagarap fayl', indonesian: 'Tidak Ada Lampiran Rusak', - nepali: 'कुनै बिग्रिएको संलग्नक छैन' + nepali: 'कुनै बिग्रिएको संलग्नक छैन', + hindi: 'कोई दूषित संलग्नक नहीं', + burmese: 'ပျက်စီးသော ပူးတွဲဖိုင်များ မရှိပါ', + thai: 'ไม่มีไฟล์แนบที่เสียหาย', + mandarin: '没有损坏的附件' }, attachmentDatabaseHealthy: { english: @@ -6114,7 +9173,13 @@ export const localizations = { tok_pisin: 'Database bilong ol fayl bilong yu i gutpela. Olgeta rekod i orait.', indonesian: 'Database lampiran Anda sehat. Semua catatan lampiran valid.', - nepali: 'तपाईंको संलग्नक डाटाबेस स्वस्थ छ। सबै संलग्नक रेकर्डहरू मान्य छन्।' + nepali: + 'तपाईंको संलग्नक डाटाबेस स्वस्थ छ। सबै संलग्नक रेकर्डहरू मान्य छन्।', + hindi: 'आपका संलग्नक डेटाबेस स्वस्थ है। सभी संलग्नक रिकॉर्ड मान्य हैं।', + burmese: + 'သင်၏ ပူးတွဲဖိုင် ဒေတာဘေ့စ်သည် ကျန်းမာသည်။ ပူးတွဲဖိုင် မှတ်တမ်းအားလုံး တရားဝင်သည်။', + thai: 'ฐานข้อมูลไฟล์แนบของคุณอยู่ในสภาพดี บันทึกไฟล์แนบทั้งหมดถูกต้อง', + mandarin: '您的附件数据库健康。所有附件记录均有效。' }, corruptedAttachments: { english: 'Corrupted Attachments', @@ -6122,7 +9187,11 @@ export const localizations = { brazilian_portuguese: 'Anexos Corrompidos', tok_pisin: 'Ol Bagarap Fayl', indonesian: 'Lampiran Rusak', - nepali: 'बिग्रिएका संलग्नकहरू' + nepali: 'बिग्रिएका संलग्नकहरू', + hindi: 'दूषित संलग्नक', + burmese: 'ပျက်စီးသော ပူးတွဲဖိုင်များ', + thai: 'ไฟล์แนบที่เสียหาย', + mandarin: '损坏的附件' }, foundCorruptedAttachments: { english: @@ -6136,7 +9205,14 @@ export const localizations = { indonesian: 'Ditemukan {count} lampiran rusak dengan URL blob di database. Ini menyebabkan kesalahan sinkronisasi dan harus dibersihkan.', nepali: - 'डाटाबेसमा blob URL भएको {count} बिग्रिएको संलग्नक फेला पारियो। यसले सिंक त्रुटिहरू निम्त्याइरहेको छ र सफा गर्नुपर्छ।' + 'डाटाबेसमा blob URL भएको {count} बिग्रिएको संलग्नक फेला पारियो। यसले सिंक त्रुटिहरू निम्त्याइरहेको छ र सफा गर्नुपर्छ।', + hindi: + 'डेटाबेस में blob URL के साथ {count} दूषित संलग्नक मिला। ये सिंक त्रुटियां पैदा कर रहे हैं और इन्हें साफ किया जाना चाहिए।', + burmese: + 'ဒေတာဘေ့စ်တွင် blob URL များပါရှိသော {count} ပျက်စီးနေသော ဖိုင်တွဲကို တွေ့ရှိပါသည်။ ဤအရာများသည် ထပ်တူပြုခြင်း အမှားများကို ဖြစ်စေပြီး ရှင်းလင်းရမည်။', + thai: 'พบไฟล์แนบที่เสียหาย {count} ไฟล์พร้อม URL ของ blob ในฐานข้อมูล สิ่งเหล่านี้กำลังทำให้เกิดข้อผิดพลาดในการซิงค์และควรทำความสะอาด', + mandarin: + '在数据库中发现 {count} 个带有 blob URL 的损坏附件。这些正在导致同步错误,应该清理。' }, foundCorruptedAttachmentsPlural: { english: @@ -6150,7 +9226,14 @@ export const localizations = { indonesian: 'Ditemukan {count} lampiran rusak dengan URL blob di database. Ini menyebabkan kesalahan sinkronisasi dan harus dibersihkan.', nepali: - 'डाटाबेसमा blob URL भएका {count} बिग्रिएका संलग्नकहरू फेला पारियो। यसले सिंक त्रुटिहरू निम्त्याइरहेको छ र सफा गर्नुपर्छ।' + 'डाटाबेसमा blob URL भएका {count} बिग्रिएका संलग्नकहरू फेला पारियो। यसले सिंक त्रुटिहरू निम्त्याइरहेको छ र सफा गर्नुपर्छ।', + hindi: + 'डेटाबेस में blob URL के साथ {count} दूषित संलग्नक मिले। ये सिंक त्रुटियां पैदा कर रहे हैं और इन्हें साफ किया जाना चाहिए।', + burmese: + 'ဒေတာဘေ့စ်တွင် blob URL များပါရှိသော {count} ပျက်စီးနေသော ဖိုင်တွဲများကို တွေ့ရှိပါသည်။ ဤအရာများသည် ထပ်တူပြုခြင်း အမှားများကို ဖြစ်စေပြီး ရှင်းလင်းရမည်။', + thai: 'พบไฟล์แนบที่เสียหาย {count} ไฟล์พร้อม URL ของ blob ในฐานข้อมูล สิ่งเหล่านี้กำลังทำให้เกิดข้อผิดพลาดในการซิงค์และควรทำความสะอาด', + mandarin: + '在数据库中发现 {count} 个带有 blob URL 的损坏附件。这些正在导致同步错误,应该清理。' }, cleanAll: { english: 'Clean All ({count})', @@ -6158,7 +9241,11 @@ export const localizations = { brazilian_portuguese: 'Limpar Tudo ({count})', tok_pisin: 'Klinim Olgeta ({count})', indonesian: 'Bersihkan Semua ({count})', - nepali: 'सबै सफा गर्नुहोस् ({count})' + nepali: 'सबै सफा गर्नुहोस् ({count})', + hindi: 'सभी साफ करें ({count})', + burmese: 'အားလုံး ရှင်းလင်းပါ ({count})', + thai: 'ทำความสะอาดทั้งหมด ({count})', + mandarin: '清理全部 ({count})' }, cleaning: { english: 'Cleaning...', @@ -6166,7 +9253,11 @@ export const localizations = { brazilian_portuguese: 'Limpando...', tok_pisin: 'Mi klinim nau...', indonesian: 'Membersihkan...', - nepali: 'सफा गर्दै...' + nepali: 'सफा गर्दै...', + hindi: 'सफाई कर रहे हैं...', + burmese: 'ရှင်းလင်းနေသည်...', + thai: 'กำลังทำความสะอาด...', + mandarin: '正在清理...' }, size: { english: 'Size', @@ -6174,7 +9265,11 @@ export const localizations = { brazilian_portuguese: 'Tamanho', tok_pisin: 'Saiz', indonesian: 'Ukuran', - nepali: 'आकार' + nepali: 'आकार', + hindi: 'आकार', + burmese: 'အရွယ်အစား', + thai: 'ขนาด', + mandarin: '大小' }, attachmentId: { english: 'Attachment ID', @@ -6182,7 +9277,11 @@ export const localizations = { brazilian_portuguese: 'ID do Anexo', tok_pisin: 'ID bilong Fayl', indonesian: 'ID Lampiran', - nepali: 'संलग्नक ID' + nepali: 'संलग्नक ID', + hindi: 'संलग्नक ID', + burmese: 'ပူးတွဲဖိုင် ID', + thai: 'รหัสไฟล์แนบ', + mandarin: '附件ID' }, localUri: { english: 'Local URI', @@ -6190,7 +9289,11 @@ export const localizations = { brazilian_portuguese: 'URI Local', tok_pisin: 'Local URI', indonesian: 'URI Lokal', - nepali: 'स्थानीय URI' + nepali: 'स्थानीय URI', + hindi: 'स्थानीय URI', + burmese: 'ဒေသတွင်း URI', + thai: 'URI ท้องถิ่น', + mandarin: '本地URI' }, associatedAssets: { english: 'Associated Assets ({count})', @@ -6198,7 +9301,11 @@ export const localizations = { brazilian_portuguese: 'Ativos Associados ({count})', tok_pisin: 'Ol Asset i go wantaim ({count})', indonesian: 'Aset Terkait ({count})', - nepali: 'सम्बद्ध एसेटहरू ({count})' + nepali: 'सम्बद्ध एसेटहरू ({count})', + hindi: 'संबद्ध एसेट ({count})', + burmese: 'ဆက်စပ်သော ပိုင်ဆိုင်မှုများ ({count})', + thai: 'สินทรัพย์ที่เกี่ยวข้อง ({count})', + mandarin: '关联资产 ({count})' }, contentLinks: { english: 'Content Links ({count})', @@ -6206,7 +9313,11 @@ export const localizations = { brazilian_portuguese: 'Links de Conteúdo ({count})', tok_pisin: 'Ol Link bilong Content ({count})', indonesian: 'Tautan Konten ({count})', - nepali: 'सामग्री लिंकहरू ({count})' + nepali: 'सामग्री लिंकहरू ({count})', + hindi: 'सामग्री लिंक ({count})', + burmese: 'အကြောင်းအရာ လင့်ခ်များ ({count})', + thai: 'ลิงก์เนื้อหา ({count})', + mandarin: '内容链接 ({count})' }, cleanThis: { english: 'Clean This', @@ -6214,7 +9325,11 @@ export const localizations = { brazilian_portuguese: 'Limpar Isto', tok_pisin: 'Klinim Dispela', indonesian: 'Bersihkan Ini', - nepali: 'यो सफा गर्नुहोस्' + nepali: 'यो सफा गर्नुहोस्', + hindi: 'इसे साफ करें', + burmese: 'ဤအရာကို ရှင်းလင်းပါ', + thai: 'ทำความสะอาดสิ่งนี้', + mandarin: '清理此项' }, cleanCorruptedAttachment: { english: 'Clean Corrupted Attachment', @@ -6222,7 +9337,11 @@ export const localizations = { brazilian_portuguese: 'Limpar Anexo Corrompido', tok_pisin: 'Klinim Bagarap Fayl', indonesian: 'Bersihkan Lampiran Rusak', - nepali: 'बिग्रिएको संलग्नक सफा गर्नुहोस्' + nepali: 'बिग्रिएको संलग्नक सफा गर्नुहोस्', + hindi: 'दूषित संलग्नक साफ करें', + burmese: 'ပျက်စီးသော ပူးတွဲဖိုင်ကို ရှင်းလင်းပါ', + thai: 'ทำความสะอาดไฟล์แนบที่เสียหาย', + mandarin: '清理损坏的附件' }, cleanCorruptedAttachmentConfirm: { english: @@ -6236,7 +9355,13 @@ export const localizations = { indonesian: 'Ini akan menghapus catatan lampiran rusak dan referensinya dari database. Tindakan ini tidak dapat dibatalkan.', nepali: - 'यसले बिग्रिएको संलग्नक रेकर्ड र यसको सन्दर्भहरू डाटाबेसबाट हटाउनेछ। यो कार्य पूर्ववत गर्न सकिँदैन।' + 'यसले बिग्रिएको संलग्नक रेकर्ड र यसको सन्दर्भहरू डाटाबेसबाट हटाउनेछ। यो कार्य पूर्ववत गर्न सकिँदैन।', + hindi: + 'यह डेटाबेस से दूषित संलग्नक रिकॉर्ड और इसके संदर्भों को हटा देगा। यह कार्य पूर्ववत नहीं किया जा सकता।', + burmese: + 'ဤအရာသည် ပျက်စီးသော ပူးတွဲဖိုင် မှတ်တမ်းနှင့် ၎င်း၏ ကိုးကားချက်များကို ဒေတာဘေ့စ်မှ ဖယ်ရှားမည်။ ဤလုပ်ဆောင်ချက်ကို ပြန်လည်ပြုပြင်နိုင်မည်မဟုတ်ပါ။', + thai: 'สิ่งนี้จะลบบันทึกไฟล์แนบที่เสียหายและการอ้างอิงจากฐานข้อมูล การกระทำนี้ไม่สามารถยกเลิกได้', + mandarin: '这将从数据库中删除损坏的附件记录及其引用。此操作无法撤销。' }, clean: { english: 'Clean', @@ -6244,7 +9369,11 @@ export const localizations = { brazilian_portuguese: 'Limpar', tok_pisin: 'Klinim', indonesian: 'Bersihkan', - nepali: 'सफा गर्नुहोस्' + nepali: 'सफा गर्नुहोस्', + hindi: 'साफ करें', + burmese: 'ရှင်းလင်းပါ', + thai: 'ทำความสะอาด', + mandarin: '清理' }, corruptedAttachmentCleanedSuccess: { english: 'Corrupted attachment cleaned successfully.', @@ -6252,7 +9381,11 @@ export const localizations = { brazilian_portuguese: 'Anexo corrompido limpo com sucesso.', tok_pisin: 'Bagarap fayl i klinim gut pinis.', indonesian: 'Lampiran rusak berhasil dibersihkan.', - nepali: 'बिग्रिएको संलग्नक सफलतापूर्वक सफा गरियो।' + nepali: 'बिग्रिएको संलग्नक सफलतापूर्वक सफा गरियो।', + hindi: 'दूषित संलग्नक सफलतापूर्वक साफ किया गया।', + burmese: 'ပျက်စီးသော ပူးတွဲဖိုင်ကို အောင်မြင်စွာ ရှင်းလင်းပြီးပါပြီ။', + thai: 'ทำความสะอาดไฟล์แนบที่เสียหายสำเร็จแล้ว', + mandarin: '损坏的附件已成功清理。' }, failedToCleanAttachment: { english: 'Failed to clean attachment: {error}', @@ -6260,7 +9393,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao limpar anexo: {error}', tok_pisin: 'I no inap klinim fayl: {error}', indonesian: 'Gagal membersihkan lampiran: {error}', - nepali: 'संलग्नक सफा गर्न असफल: {error}' + nepali: 'संलग्नक सफा गर्न असफल: {error}', + hindi: 'संलग्नक साफ करने में विफल: {error}', + burmese: 'ဖိုင်တွဲကို ရှင်းလင်း၍မရပါ: {error}', + thai: 'ล้างไฟล์แนบไม่สำเร็จ: {error}', + mandarin: '清理附件失败: {error}' }, cleanAllCorruptedAttachments: { english: 'Clean All Corrupted Attachments', @@ -6268,7 +9405,11 @@ export const localizations = { brazilian_portuguese: 'Limpar Todos os Anexos Corrompidos', tok_pisin: 'Klinim Olgeta Bagarap Fayl', indonesian: 'Bersihkan Semua Lampiran Rusak', - nepali: 'सबै बिग्रिएका संलग्नकहरू सफा गर्नुहोस्' + nepali: 'सबै बिग्रिएका संलग्नकहरू सफा गर्नुहोस्', + hindi: 'सभी दूषित संलग्नक साफ करें', + burmese: 'ပျက်စီးသော ပူးတွဲဖိုင်အားလုံးကို ရှင်းလင်းပါ', + thai: 'ทำความสะอาดไฟล์แนบที่เสียหายทั้งหมด', + mandarin: '清理所有损坏的附件' }, cleanAllConfirm: { english: @@ -6282,7 +9423,13 @@ export const localizations = { indonesian: 'Ini akan membersihkan {count} lampiran rusak. Tindakan ini tidak dapat dibatalkan.', nepali: - 'यसले {count} बिग्रिएको संलग्नक सफा गर्नेछ। यो कार्य पूर्ववत गर्न सकिँदैन।' + 'यसले {count} बिग्रिएको संलग्नक सफा गर्नेछ। यो कार्य पूर्ववत गर्न सकिँदैन।', + hindi: + 'यह {count} दूषित संलग्नक को साफ करेगा। इस कार्य को पूर्ववत नहीं किया जा सकता।', + burmese: + 'ဤအရာသည် {count} ပျက်စီးနေသော ဖိုင်တွဲကို ရှင်းလင်းပါမည်။ ဤလုပ်ဆောင်ချက်ကို ပြန်လည်ပြုပြင်ခြင်း မပြုလုပ်နိုင်ပါ။', + thai: 'นี่จะทำความสะอาดไฟล์แนบที่เสียหาย {count} ไฟล์ การกระทำนี้ไม่สามารถยกเลิกได้', + mandarin: '这将清理 {count} 个损坏的附件。此操作无法撤消。' }, cleanAllConfirmPlural: { english: @@ -6296,7 +9443,13 @@ export const localizations = { indonesian: 'Ini akan membersihkan {count} lampiran rusak. Tindakan ini tidak dapat dibatalkan.', nepali: - 'यसले {count} बिग्रिएका संलग्नकहरू सफा गर्नेछ। यो कार्य पूर्ववत गर्न सकिँदैन।' + 'यसले {count} बिग्रिएका संलग्नकहरू सफा गर्नेछ। यो कार्य पूर्ववत गर्न सकिँदैन।', + hindi: + 'यह {count} दूषित संलग्नकों को साफ करेगा। इस कार्य को पूर्ववत नहीं किया जा सकता।', + burmese: + 'ဤအရာသည် {count} ပျက်စီးနေသော ဖိုင်တွဲများကို ရှင်းလင်းပါမည်။ ဤလုပ်ဆောင်ချက်ကို ပြန်လည်ပြုပြင်ခြင်း မပြုလုပ်နိုင်ပါ။', + thai: 'นี่จะทำความสะอาดไฟล์แนบที่เสียหาย {count} ไฟล์ การกระทำนี้ไม่สามารถยกเลิกได้', + mandarin: '这将清理 {count} 个损坏的附件。此操作无法撤消。' }, partialSuccess: { english: 'Partial Success', @@ -6304,7 +9457,11 @@ export const localizations = { brazilian_portuguese: 'Sucesso Parcial', tok_pisin: 'Sampela i Orait', indonesian: 'Berhasil Sebagian', - nepali: 'आंशिक सफलता' + nepali: 'आंशिक सफलता', + hindi: 'आंशिक सफलता', + burmese: 'တစ်စိတ်တစ်ပိုင်း အောင်မြင်မှု', + thai: 'ความสำเร็จบางส่วน', + mandarin: '部分成功' }, cleanedAttachmentsWithErrors: { english: @@ -6316,7 +9473,12 @@ export const localizations = { tok_pisin: 'Klinim {cleaned} fayl. {errorCount} rong i kamap:\n\n{errors}', indonesian: 'Membersihkan {cleaned} lampiran. {errorCount} kesalahan terjadi:\n\n{errors}', - nepali: '{cleaned} संलग्नक सफा गरियो। {errorCount} त्रुटि भयो:\n\n{errors}' + nepali: '{cleaned} संलग्नक सफा गरियो। {errorCount} त्रुटि भयो:\n\n{errors}', + hindi: '{cleaned} संलग्नक साफ किया। {errorCount} त्रुटि हुई:\n\n{errors}', + burmese: + '{cleaned} ပူးတွဲဖိုင်ကို သန့်ရှင်းပြီး။ {errorCount} အမှား ဖြစ်ပွားခဲ့သည်:\n\n{errors}', + thai: 'ล้างไฟล์แนบ {cleaned} รายการ เกิดข้อผิดพลาด {errorCount} รายการ:\n\n{errors}', + mandarin: '已清理 {cleaned} 个附件。发生 {errorCount} 个错误:\n\n{errors}' }, cleanedAttachmentsWithErrorsPlural: { english: @@ -6329,7 +9491,13 @@ export const localizations = { indonesian: 'Membersihkan {cleaned} lampiran. {errorCount} kesalahan terjadi:\n\n{errors}', nepali: - '{cleaned} संलग्नकहरू सफा गरियो। {errorCount} त्रुटिहरू भयो:\n\n{errors}' + '{cleaned} संलग्नकहरू सफा गरियो। {errorCount} त्रुटिहरू भयो:\n\n{errors}', + hindi: + '{cleaned} संलग्नक साफ किए। {errorCount} त्रुटियां हुईं:\n\n{errors}', + burmese: + '{cleaned} ပူးတွဲဖိုင်များကို သန့်ရှင်းပြီး။ {errorCount} အမှားများ ဖြစ်ပွားခဲ့သည်:\n\n{errors}', + thai: 'ล้างไฟล์แนบ {cleaned} รายการ เกิดข้อผิดพลาด {errorCount} รายการ:\n\n{errors}', + mandarin: '已清理 {cleaned} 个附件。发生 {errorCount} 个错误:\n\n{errors}' }, successfullyCleanedAttachments: { english: 'Successfully cleaned {cleaned} corrupted attachment.', @@ -6337,7 +9505,12 @@ export const localizations = { brazilian_portuguese: 'Limpou com sucesso {cleaned} anexo corrompido.', tok_pisin: 'Klinim gut {cleaned} bagarap fayl.', indonesian: 'Berhasil membersihkan {cleaned} lampiran rusak.', - nepali: '{cleaned} बिग्रिएको संलग्नक सफलतापूर्वक सफा गरियो।' + nepali: '{cleaned} बिग्रिएको संलग्नक सफलतापूर्वक सफा गरियो।', + hindi: '{cleaned} दूषित संलग्नक सफलतापूर्वक साफ किया गया।', + burmese: + '{cleaned} ပျက်စီးသော ပူးတွဲဖိုင်ကို အောင်မြင်စွာ သန့်ရှင်းပြီးပါပြီ။', + thai: 'ล้างไฟล์แนบที่เสียหาย {cleaned} รายการสำเร็จ', + mandarin: '已成功清理 {cleaned} 个损坏的附件。' }, successfullyCleanedAttachmentsPlural: { english: 'Successfully cleaned {cleaned} corrupted attachments.', @@ -6345,7 +9518,12 @@ export const localizations = { brazilian_portuguese: 'Limpou com sucesso {cleaned} anexos corrompidos.', tok_pisin: 'Klinim gut {cleaned} bagarap fayl.', indonesian: 'Berhasil membersihkan {cleaned} lampiran rusak.', - nepali: '{cleaned} बिग्रिएका संलग्नकहरू सफलतापूर्वक सफा गरियो।' + nepali: '{cleaned} बिग्रिएका संलग्नकहरू सफलतापूर्वक सफा गरियो।', + hindi: '{cleaned} दूषित संलग्नक सफलतापूर्वक साफ किए गए।', + burmese: + '{cleaned} ပျက်စီးသော ပူးတွဲဖိုင်များကို အောင်မြင်စွာ သန့်ရှင်းပြီးပါပြီ။', + thai: 'ล้างไฟล์แนบที่เสียหาย {cleaned} รายการสำเร็จ', + mandarin: '已成功清理 {cleaned} 个损坏的附件。' }, failedToCleanAttachments: { english: 'Failed to clean attachments: {error}', @@ -6353,7 +9531,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao limpar anexos: {error}', tok_pisin: 'I no inap klinim ol fayl: {error}', indonesian: 'Gagal membersihkan lampiran: {error}', - nepali: 'संलग्नकहरू सफा गर्न असफल: {error}' + nepali: 'संलग्नकहरू सफा गर्न असफल: {error}', + hindi: 'संलग्नक साफ करने में विफल: {error}', + burmese: 'ပူးတွဲဖိုင်များကို သန့်ရှင်းရန် မအောင်မြင်ပါ: {error}', + thai: 'ล้างไฟล์แนบไม่สำเร็จ: {error}', + mandarin: '清理附件失败: {error}' }, failedToLoadCorruptedAttachments: { english: 'Failed to load corrupted attachments. Please try again.', @@ -6363,7 +9545,12 @@ export const localizations = { 'Falha ao carregar anexos corrompidos. Por favor, tente novamente.', tok_pisin: 'I no inap loadim ol bagarap fayl. Plis traim gen.', indonesian: 'Gagal memuat lampiran rusak. Silakan coba lagi.', - nepali: 'बिग्रिएका संलग्नकहरू लोड गर्न असफल। कृपया पुन: प्रयास गर्नुहोस्।' + nepali: 'बिग्रिएका संलग्नकहरू लोड गर्न असफल। कृपया पुन: प्रयास गर्नुहोस्।', + hindi: 'दूषित संलग्नक लोड करने में विफल। कृपया पुनः प्रयास करें।', + burmese: + 'ပျက်စီးသော ပူးတွဲဖိုင်များကို လုပ်ဆောင်ရန် မအောင်မြင်ပါ။ ကျေးဇူးပြု၍ ထပ်မံကြိုးစားပါ။', + thai: 'โหลดไฟล์แนบที่เสียหายไม่สำเร็จ กรุณาลองอีกครั้ง', + mandarin: '加载损坏的附件失败。请重试。' }, unnamed: { english: 'Unnamed', @@ -6371,7 +9558,11 @@ export const localizations = { brazilian_portuguese: 'Sem nome', tok_pisin: 'I no gat nem', indonesian: 'Tanpa nama', - nepali: 'नाम नभएको' + nepali: 'नाम नभएको', + hindi: 'बिना नाम', + burmese: 'အမည်မဲ့', + thai: 'ไม่มีชื่อ', + mandarin: '未命名' }, backToProjects: { english: 'Back to Projects', @@ -6379,7 +9570,11 @@ export const localizations = { brazilian_portuguese: 'Voltar aos Projetos', tok_pisin: 'Go bek long ol Projek', indonesian: 'Kembali ke Proyek', - nepali: 'प्रोजेक्टहरूमा फर्कनुहोस्' + nepali: 'प्रोजेक्टहरूमा फर्कनुहोस्', + hindi: 'परियोजनाओं पर वापस', + burmese: 'စီမံကိန်းများသို့ ပြန်သွားပါ', + thai: 'กลับไปที่โครงการ', + mandarin: '返回项目' }, downloaded: { english: 'Downloaded', @@ -6387,7 +9582,11 @@ export const localizations = { brazilian_portuguese: 'Baixado', tok_pisin: 'Downloaded', indonesian: 'Diunduh', - nepali: 'डाउनलोड भयो' + nepali: 'डाउनलोड भयो', + hindi: 'डाउनलोड हो गया', + burmese: 'ဒေါင်းလုဒ်ပြီးပါပြီ', + thai: 'ดาวน์โหลดแล้ว', + mandarin: '已下载' }, freeUpSpace: { english: 'Free Up Space', @@ -6395,7 +9594,11 @@ export const localizations = { brazilian_portuguese: 'Liberar Espaço', tok_pisin: 'Free Up Space', indonesian: 'Bebaskan Ruang', - nepali: 'ठाउँ खाली गर्नुहोस्' + nepali: 'ठाउँ खाली गर्नुहोस्', + hindi: 'जगह खाली करें', + burmese: 'နေရာ လွတ်မြောက်ပါ', + thai: 'ปล่อยพื้นที่', + mandarin: '释放空间' }, storageUsed: { english: 'Storage Used', @@ -6403,7 +9606,11 @@ export const localizations = { brazilian_portuguese: 'Espaço Usado', tok_pisin: 'Storage Used', indonesian: 'Penyimpanan yang Digunakan', - nepali: 'प्रयोग भएको भण्डारण' + nepali: 'प्रयोग भएको भण्डारण', + hindi: 'उपयोग की गई संग्रहण', + burmese: 'အသုံးပြုထားသော သိုလှောင်မှု', + thai: 'พื้นที่จัดเก็บที่ใช้', + mandarin: '已用存储' }, notDownloaded: { english: 'Not Downloaded', @@ -6411,7 +9618,11 @@ export const localizations = { brazilian_portuguese: 'Não Baixado', tok_pisin: 'Not Downloaded', indonesian: 'Tidak Diunduh', - nepali: 'डाउनलोड भएको छैन' + nepali: 'डाउनलोड भएको छैन', + hindi: 'डाउनलोड नहीं हुआ', + burmese: 'ဒေါင်းလုဒ်မလုပ်ရသေးပါ', + thai: 'ยังไม่ได้ดาวน์โหลด', + mandarin: '未下载' }, missingCloudData: { english: 'Missing Cloud Data', @@ -6419,7 +9630,11 @@ export const localizations = { brazilian_portuguese: 'Dados na Nuvem Faltando', tok_pisin: 'No gat ol data long cloud', indonesian: 'Data Cloud Hilang', - nepali: 'क्लाउड डाटा हराइरहेको छ' + nepali: 'क्लाउड डाटा हराइरहेको छ', + hindi: 'क्लाउड डेटा गायब', + burmese: 'Cloud ဒေတာ ပျောက်ဆုံးနေသည်', + thai: 'ข้อมูลคลาวด์หายไป', + mandarin: '云数据缺失' }, deleteAccount: { english: 'Delete Account', @@ -6427,7 +9642,11 @@ export const localizations = { brazilian_portuguese: 'Excluir Conta', tok_pisin: 'Rausim Account', indonesian: 'Hapus Akun', - nepali: 'खाता मेटाउनुहोस्' + nepali: 'खाता मेटाउनुहोस्', + hindi: 'खाता हटाएं', + burmese: 'အကောင့်ကို ဖျက်ပါ', + thai: 'ลบบัญชี', + mandarin: '删除账户' }, accountDeletionTitle: { english: 'Delete Your Account', @@ -6435,7 +9654,11 @@ export const localizations = { brazilian_portuguese: 'Excluir Sua Conta', tok_pisin: 'Rausim Account Bilong Yu', indonesian: 'Hapus Akun Anda', - nepali: 'आफ्नो खाता मेटाउनुहोस्' + nepali: 'आफ्नो खाता मेटाउनुहोस्', + hindi: 'अपना खाता हटाएं', + burmese: 'သင်၏ အကောင့်ကို ဖျက်ပါ', + thai: 'ลบบัญชีของคุณ', + mandarin: '删除您的账户' }, accountDeletionWarning: { english: @@ -6449,7 +9672,14 @@ export const localizations = { indonesian: 'Setelah menghapus akun Anda, Anda tidak akan dapat mendaftar atau masuk saat offline. Anda harus online untuk membuat akun baru atau masuk.', nepali: - 'आफ्नो खाता मेटाएपछि, तपाईं अफलाइन हुँदा दर्ता वा लग इन गर्न सक्नुहुने छैन। नयाँ खाता बनाउन वा लग इन गर्न तपाईं अनलाइन हुनुपर्छ।' + 'आफ्नो खाता मेटाएपछि, तपाईं अफलाइन हुँदा दर्ता वा लग इन गर्न सक्नुहुने छैन। नयाँ खाता बनाउन वा लग इन गर्न तपाईं अनलाइन हुनुपर्छ।', + hindi: + 'खाता हटाने के बाद, आप ऑफलाइन होने पर पंजीकरण या लॉग इन नहीं कर सकेंगे। नया खाता बनाने या लॉग इन करने के लिए आपको ऑनलाइन होना चाहिए।', + burmese: + 'သင်၏ အကောင့်ကို ဖျက်ပြီးနောက်၊ အင်တာနက် မရှိသောအခါ မှတ်ပုံတင်ခြင်း သို့မဟုတ် လော့ဂ်အင်လုပ်ခြင်း မပြုလုပ်နိုင်ပါ။ အကောင့်အသစ် ဖန်တီးရန် သို့မဟုတ် လော့ဂ်အင်လုပ်ရန် အင်တာနက် ချိတ်ဆက်ရပါမည်။', + thai: 'หลังจากลบบัญชีแล้ว คุณจะไม่สามารถลงทะเบียนหรือเข้าสู่ระบบแบบออฟไลน์ได้ คุณต้องออนไลน์เพื่อสร้างบัญชีใหม่หรือเข้าสู่ระบบ', + mandarin: + '删除账户后,您将无法在离线时注册或登录。您必须在线才能创建新账户或登录。' }, accountDeletionPIIWarning: { english: @@ -6463,7 +9693,14 @@ export const localizations = { indonesian: 'Akun Anda akan dinonaktifkan (penghapusan lunak). Semua data Anda akan dilestarikan, tetapi Anda tidak akan dapat mengakses aplikasi hingga Anda memulihkan akun Anda. Anda dapat memulihkan akun Anda kapan saja, tetapi Anda harus online untuk melakukannya.', nepali: - 'तपाईंको खाता निष्क्रिय गरिनेछ (सफ्ट डिलिट)। तपाईंको सबै डाटा सुरक्षित रहनेछ, तर तपाईंले आफ्नो खाता पुनर्स्थापना नगरेसम्म एप पहुँच गर्न सक्नुहुने छैन। तपाईं जुनसुकै बेला आफ्नो खाता पुनर्स्थापना गर्न सक्नुहुन्छ, तर त्यसका लागि तपाईं अनलाइन हुनुपर्छ।' + 'तपाईंको खाता निष्क्रिय गरिनेछ (सफ्ट डिलिट)। तपाईंको सबै डाटा सुरक्षित रहनेछ, तर तपाईंले आफ्नो खाता पुनर्स्थापना नगरेसम्म एप पहुँच गर्न सक्नुहुने छैन। तपाईं जुनसुकै बेला आफ्नो खाता पुनर्स्थापना गर्न सक्नुहुन्छ, तर त्यसका लागि तपाईं अनलाइन हुनुपर्छ।', + hindi: + 'आपका खाता निष्क्रिय कर दिया जाएगा (सॉफ्ट डिलीट)। आपका सभी डेटा सुरक्षित रहेगा, लेकिन जब तक आप अपना खाता पुनर्स्थापित नहीं करते तब तक आप ऐप तक पहुंच नहीं सकेंगे। आप किसी भी समय अपना खाता पुनर्स्थापित कर सकते हैं, लेकिन इसके लिए आपको ऑनलाइन होना चाहिए।', + burmese: + 'သင်၏ အကောင့်ကို ပိတ်သိမ်းမည် (ပျော့ပျောင်းဖျက်ခြင်း)။ သင်၏ ဒေတာအားလုံး ထိန်းသိမ်းမည်၊ သို့သော် သင်၏ အကောင့်ကို ပြန်လည်ရယူမသည်အထိ အက်ပ်ကို ဝင်ရောက်ခွင့် မရပါ။ မည်သည့်အချိန်မဆို အကောင့်ကို ပြန်လည်ရယူနိုင်ပါသည်၊ သို့သော် အင်တာနက် ချိတ်ဆက်ရပါမည်။', + thai: 'บัญชีของคุณจะถูกปิดใช้งาน (การลบแบบนุ่มนวล) ข้อมูลทั้งหมดจะถูกเก็บไว้ แต่คุณจะไม่สามารถเข้าถึงแอปได้จนกว่าคุณจะกู้คืนบัญชี คุณสามารถกู้คืนบัญชีได้ตลอดเวลา แต่ต้องออนไลน์อยู่', + mandarin: + '您的账户将被停用(软删除)。您的所有数据将被保留,但在您恢复账户之前您将无法访问应用。您可以随时恢复账户,但必须在线。' }, accountDeletionContributionsInfo: { english: @@ -6477,7 +9714,14 @@ export const localizations = { indonesian: 'Semua kontribusi Anda (proyek, quest, aset, terjemahan, suara) akan dilestarikan dan akan tetap publik sesuai dengan syarat yang telah Anda setujui saat bergabung. Akun Anda dapat dipulihkan kapan saja, dan semua data Anda akan dapat diakses lagi.', nepali: - 'तपाईंका सबै योगदानहरू (प्रोजेक्टहरू, क्वेस्टहरू, एसेटहरू, अनुवादहरू, मतहरू) सुरक्षित रहनेछन् र तपाईंले सामेल हुँदा सहमत भएका सर्तहरू अनुसार सार्वजनिक रहनेछन्। तपाईंको खाता जुनसुकै बेला पुनर्स्थापना गर्न सकिन्छ, र तपाईंको सबै डाटा फेरि पहुँचयोग्य हुनेछ।' + 'तपाईंका सबै योगदानहरू (प्रोजेक्टहरू, क्वेस्टहरू, एसेटहरू, अनुवादहरू, मतहरू) सुरक्षित रहनेछन् र तपाईंले सामेल हुँदा सहमत भएका सर्तहरू अनुसार सार्वजनिक रहनेछन्। तपाईंको खाता जुनसुकै बेला पुनर्स्थापना गर्न सकिन्छ, र तपाईंको सबै डाटा फेरि पहुँचयोग्य हुनेछ।', + hindi: + 'आपके सभी योगदान (परियोजनाएं, क्वेस्ट, एसेट, अनुवाद, वोट) सुरक्षित रहेंगे और शामिल होने पर आपकी सहमति के अनुसार सार्वजनिक रहेंगे। आपका खाता किसी भी समय पुनर्स्थापित किया जा सकता है, और आपका सभी डेटा फिर से पहुंच योग्य होगा।', + burmese: + 'သင်၏ ပါဝင်ဆောင်ရွက်မှုအားလုံး (စီမံကိန်းများ၊ ခရီးစဉ်များ၊ ပိုင်ဆိုင်မှုများ၊ ဘာသာပြန်ဆိုချက်များ၊ မဲများ) ထိန်းသိမ်းမည်၊ ပါဝင်ချိန်တွင် သဘောတူထားသော စည်းမျဉ်းများအရ များများပြည်သူမြင်နိုင်မည်။ မည်သည့်အချိန်မဆို အကောင့်ကို ပြန်လည်ရယူနိုင်ပါသည်။', + thai: 'การมีส่วนร่วมทั้งหมดของคุณ (โครงการ ภารกิจ สินทรัพย์ การแปล votes) จะถูกเก็บรักษาและจะยังคงเป็นสาธารณะตามข้อกำหนดที่คุณได้ตกลงเมื่อเข้าร่วม คุณสามารถกู้คืนบัญชีได้ตลอดเวลา และข้อมูลทั้งหมดจะเข้าถึงได้อีกครั้ง', + mandarin: + '您的所有贡献(项目、任务、资产、翻译、投票)将被保留,并将根据您加入时同意的条款保持公开。您可以随时恢复账户,所有数据将再次可访问。' }, accountDeletionConfirm: { english: @@ -6491,7 +9735,13 @@ export const localizations = { indonesian: 'Apakah Anda benar-benar yakin ingin menghapus akun Anda? Anda dapat memulihkannya nanti, tetapi Anda harus online untuk melakukannya.', nepali: - 'के तपाईं आफ्नो खाता मेटाउन निश्चित हुनुहुन्छ? तपाईं यसलाई पछि पुनर्स्थापना गर्न सक्नुहुन्छ, तर त्यसका लागि तपाईं अनलाइन हुनुपर्छ।' + 'के तपाईं आफ्नो खाता मेटाउन निश्चित हुनुहुन्छ? तपाईं यसलाई पछि पुनर्स्थापना गर्न सक्नुहुन्छ, तर त्यसका लागि तपाईं अनलाइन हुनुपर्छ।', + hindi: + 'क्या आप वाकई अपना खाता हटाना चाहते हैं? आप इसे बाद में पुनर्स्थापित कर सकते हैं, लेकिन इसके लिए आपको ऑनलाइन होना चाहिए।', + burmese: + 'သင်၏ အကောင့်ကို ဖျက်ရန် သေချာပါသလား? နောက်မှ ပြန်လည်ရယူနိုင်ပါသည်၊ သို့သော် အင်တာနက် ချိတ်ဆက်ရပါမည်။', + thai: 'คุณแน่ใจหรือไม่ว่าต้องการลบบัญชี? คุณสามารถกู้คืนได้ในภายหลัง แต่ต้องออนไลน์อยู่', + mandarin: '您确定要删除您的账户吗?您可以稍后恢复,但必须在线才能操作。' }, accountDeletionConfirmMessage: { english: @@ -6505,7 +9755,14 @@ export const localizations = { indonesian: 'Akun Anda akan dihapus (penghapusan lunak). Anda dapat memulihkannya nanti dari layar login, tetapi Anda harus online untuk memulihkannya.', nepali: - 'तपाईंको खाता मेटाइनेछ (सफ्ट डिलिट)। तपाईं यसलाई पछि लगइन स्क्रिनबाट पुनर्स्थापना गर्न सक्नुहुन्छ, तर पुनर्स्थापना गर्न तपाईं अनलाइन हुनुपर्छ।' + 'तपाईंको खाता मेटाइनेछ (सफ्ट डिलिट)। तपाईं यसलाई पछि लगइन स्क्रिनबाट पुनर्स्थापना गर्न सक्नुहुन्छ, तर पुनर्स्थापना गर्न तपाईं अनलाइन हुनुपर्छ।', + hindi: + 'आपका खाता हटा दिया जाएगा (सॉफ्ट डिलीट)। आप इसे बाद में लॉगिन स्क्रीन से पुनर्स्थापित कर सकते हैं, लेकिन पुनर्स्थापित करने के लिए आपको ऑनलाइन होना चाहिए।', + burmese: + 'သင်၏ အကောင့်ကို ဖျက်မည် (ပျော့ပျောင်းဖျက်ခြင်း)။ လော့ဂ်အင်စခရင်မှ ပြန်လည်ရယူနိုင်ပါသည်၊ သို့သော် အင်တာနက် ချိတ်ဆက်ရပါမည်။', + thai: 'บัญชีของคุณจะถูกลบ (การลบแบบนุ่มนวล) คุณสามารถกู้คืนได้จากหน้าจอเข้าสู่ระบบในภายหลัง แต่ต้องออนไลน์อยู่เพื่อกู้คืน', + mandarin: + '您的账户将被删除(软删除)。您可以稍后从登录屏幕恢复,但必须在线才能恢复。' }, accountDeletionStep1Title: { english: 'Step 1: Understand the Consequences', @@ -6513,7 +9770,11 @@ export const localizations = { brazilian_portuguese: 'Etapa 1: Entender as Consequências', tok_pisin: 'Step 1: Save ol Samting Bai Kamap', indonesian: 'Langkah 1: Pahami Konsekuensinya', - nepali: 'चरण १: परिणामहरू बुझ्नुहोस्' + nepali: 'चरण १: परिणामहरू बुझ्नुहोस्', + hindi: 'चरण 1: परिणाम समझें', + burmese: 'အဆင့် ၁: ရလဒ်များကို နားလည်ပါ', + thai: 'ขั้นตอนที่ 1: ทำความเข้าใจผลที่ตามมา', + mandarin: '步骤 1:了解后果' }, accountDeletionStep2Title: { english: 'Step 2: Final Confirmation', @@ -6521,7 +9782,11 @@ export const localizations = { brazilian_portuguese: 'Etapa 2: Confirmação Final', tok_pisin: 'Step 2: Final Confirm', indonesian: 'Langkah 2: Konfirmasi Akhir', - nepali: 'चरण २: अन्तिम पुष्टि' + nepali: 'चरण २: अन्तिम पुष्टि', + hindi: 'चरण 2: अंतिम पुष्टि', + burmese: 'အဆင့် ၂: နောက်ဆုံး အတည်ပြုချက်', + thai: 'ขั้นตอนที่ 2: การยืนยันครั้งสุดท้าย', + mandarin: '步骤 2:最终确认' }, accountDeletionSuccess: { english: @@ -6535,7 +9800,14 @@ export const localizations = { indonesian: 'Akun Anda telah berhasil dihapus (penghapusan lunak). Anda dapat memulihkannya nanti, tetapi Anda harus online untuk melakukannya. Anda akan keluar sekarang.', nepali: - 'तपाईंको खाता सफलतापूर्वक मेटाइयो (सफ्ट डिलिट)। तपाईं यसलाई पछि पुनर्स्थापना गर्न सक्नुहुन्छ, तर त्यसका लागि तपाईं अनलाइन हुनुपर्छ। तपाईं अब साइन आउट हुनुहुनेछ।' + 'तपाईंको खाता सफलतापूर्वक मेटाइयो (सफ्ट डिलिट)। तपाईं यसलाई पछि पुनर्स्थापना गर्न सक्नुहुन्छ, तर त्यसका लागि तपाईं अनलाइन हुनुपर्छ। तपाईं अब साइन आउट हुनुहुनेछ।', + hindi: + 'आपका खाता सफलतापूर्वक हटा दिया गया (सॉफ्ट डिलीट)। आप इसे बाद में पुनर्स्थापित कर सकते हैं, लेकिन इसके लिए आपको ऑनलाइन होना चाहिए। अब आप साइन आउट हो जाएंगे।', + burmese: + 'သင်၏ အကောင့်ကို အောင်မြင်စွာ ဖျက်ပြီးပါပြီ (ပျော့ပျောင်းဖျက်ခြင်း)။ နောက်မှ ပြန်လည်ရယူနိုင်ပါသည်၊ သို့သော် အင်တာနက် ချိတ်ဆက်ရပါမည်။ ယခု လော့ဂ်အောက်ထွက်မည်။', + thai: 'บัญชีของคุณถูกลบสำเร็จแล้ว (การลบแบบนุ่มนวล) คุณสามารถกู้คืนได้ในภายหลัง แต่ต้องออนไลน์อยู่ คุณจะถูกออกจากระบบตอนนี้', + mandarin: + '您的账户已成功删除(软删除)。您可以稍后恢复,但必须在线。您现在将被登出。' }, accountDeletionError: { english: 'Failed to delete account: {error}', @@ -6543,7 +9815,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao excluir conta: {error}', tok_pisin: 'I no inap rausim account: {error}', indonesian: 'Gagal menghapus akun: {error}', - nepali: 'खाता मेटाउन असफल: {error}' + nepali: 'खाता मेटाउन असफल: {error}', + hindi: 'खाता हटाने में विफल: {error}', + burmese: 'အကောင့်ဖျက်ရန် မအောင်မြင်ပါ: {error}', + thai: 'ลบบัญชีไม่สำเร็จ: {error}', + mandarin: '删除账户失败: {error}' }, accountDeletedTitle: { english: 'Account Deleted', @@ -6551,7 +9827,11 @@ export const localizations = { brazilian_portuguese: 'Conta Excluída', tok_pisin: 'Account i Raus', indonesian: 'Akun Dihapus', - nepali: 'खाता मेटाइयो' + nepali: 'खाता मेटाइयो', + hindi: 'खाता हटा दिया गया', + burmese: 'အကောင့် ဖျက်ပြီးပါပြီ', + thai: 'บัญชีถูกลบแล้ว', + mandarin: '账户已删除' }, accountDeletedMessage: { english: @@ -6565,7 +9845,14 @@ export const localizations = { indonesian: 'Akun Anda telah dihapus. Anda dapat memulihkannya untuk mendapatkan kembali akses ke semua data Anda, atau Anda dapat keluar dan kembali ke layar login.', nepali: - 'तपाईंको खाता मेटाइएको छ। तपाईं आफ्नो सबै डाटामा पहुँच पुन: प्राप्त गर्न यसलाई पुनर्स्थापना गर्न सक्नुहुन्छ, वा तपाईं लगआउट गरेर लगइन स्क्रिनमा फर्कन सक्नुहुन्छ।' + 'तपाईंको खाता मेटाइएको छ। तपाईं आफ्नो सबै डाटामा पहुँच पुन: प्राप्त गर्न यसलाई पुनर्स्थापना गर्न सक्नुहुन्छ, वा तपाईं लगआउट गरेर लगइन स्क्रिनमा फर्कन सक्नुहुन्छ।', + hindi: + 'आपका खाता हटा दिया गया है। आप इसे पुनर्स्थापित कर अपने सभी डेटा तक पहुंच पा सकते हैं, या लॉग आउट करके लॉगिन स्क्रीन पर वापस जा सकते हैं।', + burmese: + 'သင်၏ အကောင့်ကို ဖျက်ပြီးပါပြီ။ ဒေတာအားလုံး ပြန်လည်ရယူရန် ပြန်လည်ရယူနိုင်ပါသည်၊ သို့မဟုတ် လော့ဂ်အောက်ထွက်၍ လော့ဂ်အင်စခရင်သို့ ပြန်သွားနိုင်ပါသည်။', + thai: 'บัญชีของคุณถูกลบแล้ว คุณสามารถกู้คืนเพื่อเข้าถึงข้อมูลทั้งหมดของคุณได้ หรือออกจากระบบและกลับไปหน้าจอเข้าสู่ระบบ', + mandarin: + '您的账户已删除。您可以恢复它以重新访问所有数据,或登出并返回登录屏幕。' }, restoreAccount: { english: 'Restore Account', @@ -6573,7 +9860,11 @@ export const localizations = { brazilian_portuguese: 'Restaurar Conta', tok_pisin: 'Restore Account', indonesian: 'Pulihkan Akun', - nepali: 'खाता पुनर्स्थापना गर्नुहोस्' + nepali: 'खाता पुनर्स्थापना गर्नुहोस्', + hindi: 'खाता पुनर्स्थापित करें', + burmese: 'အကောင့်ကို ပြန်လည်ရယူပါ', + thai: 'กู้คืนบัญชี', + mandarin: '恢复账户' }, restoreAccountConfirmTitle: { english: 'Restore Account?', @@ -6581,7 +9872,11 @@ export const localizations = { brazilian_portuguese: 'Restaurar Conta?', tok_pisin: 'Restore Account?', indonesian: 'Pulihkan Akun?', - nepali: 'खाता पुनर्स्थापना गर्ने?' + nepali: 'खाता पुनर्स्थापना गर्ने?', + hindi: 'खाता पुनर्स्थापित करें?', + burmese: 'အကောင့်ကို ပြန်လည်ရယူမလား?', + thai: 'กู้คืนบัญชี?', + mandarin: '恢复账户?' }, restoreAccountConfirmMessage: { english: @@ -6595,7 +9890,14 @@ export const localizations = { indonesian: 'Akun Anda akan dipulihkan sepenuhnya. Semua data Anda akan dapat diakses lagi, dan Anda dapat melanjutkan menggunakan aplikasi secara normal.', nepali: - 'तपाईंको खाता पूर्ण रूपमा पुनर्स्थापना गरिनेछ। तपाईंको सबै डाटा फेरि पहुँचयोग्य हुनेछ, र तपाईं सामान्य रूपमा एप प्रयोग जारी राख्न सक्नुहुन्छ।' + 'तपाईंको खाता पूर्ण रूपमा पुनर्स्थापना गरिनेछ। तपाईंको सबै डाटा फेरि पहुँचयोग्य हुनेछ, र तपाईं सामान्य रूपमा एप प्रयोग जारी राख्न सक्नुहुन्छ।', + hindi: + 'आपका खाता पूरी तरह से पुनर्स्थापित हो जाएगा। आपका सभी डेटा फिर से पहुंच योग्य होगा, और आप सामान्य रूप से ऐप का उपयोग जारी रख सकते हैं।', + burmese: + 'သင်၏ အကောင့်ကို ပြည့်စုံစွာ ပြန်လည်ရယူမည်။ ဒေတာအားလုံး ပြန်လည်ဝင်ရောက်နိုင်မည်၊ အက်ပ်ကို ပုံမှန်ဆက်သုံးနိုင်ပါသည်။', + thai: 'บัญชีของคุณจะถูกกู้คืนอย่างสมบูรณ์ ข้อมูลทั้งหมดจะเข้าถึงได้อีกครั้ง และคุณสามารถใช้แอปได้ตามปกติ', + mandarin: + '您的账户将被完全恢复。您的所有数据将再次可访问,您可以继续正常使用应用。' }, accountRestoreSuccess: { english: 'Your account has been successfully restored. Welcome back!', @@ -6604,7 +9906,12 @@ export const localizations = { 'Sua conta foi restaurada com sucesso. Bem-vindo de volta!', tok_pisin: 'Account bilong yu i restore pinis. Welkam bek!', indonesian: 'Akun Anda telah berhasil dipulihkan. Selamat datang kembali!', - nepali: 'तपाईंको खाता सफलतापूर्वक पुनर्स्थापना गरियो। फेरि स्वागत छ!' + nepali: 'तपाईंको खाता सफलतापूर्वक पुनर्स्थापना गरियो। फेरि स्वागत छ!', + hindi: 'आपका खाता सफलतापूर्वक पुनर्स्थापित हो गया। वापस स्वागत है!', + burmese: + 'သင်၏ အကောင့်ကို အောင်မြင်စွာ ပြန်လည်ရယူပြီးပါပြီ။ ပြန်လာသည့်အတွက် ကြိုဆိုပါသည်!', + thai: 'บัญชีของคุณถูกกู้คืนสำเร็จแล้ว ยินดีต้อนรับกลับ!', + mandarin: '您的账户已成功恢复。欢迎回来!' }, accountRestoreError: { english: 'Failed to restore account: {error}', @@ -6612,7 +9919,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao restaurar conta: {error}', tok_pisin: 'I no inap restore account: {error}', indonesian: 'Gagal memulihkan akun: {error}', - nepali: 'खाता पुनर्स्थापना गर्न असफल: {error}' + nepali: 'खाता पुनर्स्थापना गर्न असफल: {error}', + hindi: 'खाता पुनर्स्थापित करने में विफल: {error}', + burmese: 'အကောင့်ပြန်လည်ရယူရန် မအောင်မြင်ပါ: {error}', + thai: 'กู้คืนบัญชีไม่สำเร็จ: {error}', + mandarin: '恢复账户失败: {error}' }, signInRequired: { english: 'Sign In Required', @@ -6620,7 +9931,11 @@ export const localizations = { brazilian_portuguese: 'Login Necessário', tok_pisin: 'Mas I Mas Sign In', indonesian: 'Masuk Diperlukan', - nepali: 'साइन इन आवश्यक छ' + nepali: 'साइन इन आवश्यक छ', + hindi: 'साइन इन आवश्यक', + burmese: 'လော့ဂ်အင်လုပ်ရန် လိုအပ်သည်', + thai: 'ต้องเข้าสู่ระบบ', + mandarin: '需要登录' }, blockContentLoginMessage: { english: @@ -6634,7 +9949,14 @@ export const localizations = { indonesian: 'Kami menyimpan informasi tentang apa yang akan diblokir di akun Anda. Silakan daftar untuk memastikan konten yang diblokir dapat disembunyikan dengan benar.', nepali: - 'हामी तपाईंको खातामा के ब्लक गर्ने बारे जानकारी भण्डारण गर्छौं। कृपया ब्लक गरिएको सामग्री राम्ररी लुकाउन सकिने सुनिश्चित गर्न दर्ता गर्नुहोस्।' + 'हामी तपाईंको खातामा के ब्लक गर्ने बारे जानकारी भण्डारण गर्छौं। कृपया ब्लक गरिएको सामग्री राम्ररी लुकाउन सकिने सुनिश्चित गर्न दर्ता गर्नुहोस्।', + hindi: + 'हम आपके खाते में क्या ब्लॉक करना है इसकी जानकारी संग्रहीत करते हैं। ब्लॉक की गई सामग्री को ठीक से छिपाने के लिए कृपया पंजीकरण करें।', + burmese: + 'သင်၏ အကောင့်တွင် မည်သည့်အရာကို ပိတ်ဆို့မည်ကို ကျွန်ုပ်တို့ သိမ်းဆည်းပါသည်။ ပိတ်ဆို့ထားသော အကြောင်းအရာကို သင့်လျော်စွာ ဖျောက်ထားနိုင်ရန် ကျေးဇူးပြု၍ မှတ်ပုံတင်ပါ။', + thai: 'เราเก็บข้อมูลเกี่ยวกับสิ่งที่ต้องบล็อกในบัญชีของคุณ กรุณาลงทะเบียนเพื่อให้แน่ใจว่าสามารถซ่อนเนื้อหาที่ถูกบล็อกได้อย่างถูกต้อง', + mandarin: + '我们在您的账户中存储有关要屏蔽内容的信息。请注册以确保被屏蔽的内容可以正确隐藏。' }, connected: { english: 'Connected', @@ -6642,7 +9964,11 @@ export const localizations = { brazilian_portuguese: 'Conectado', tok_pisin: 'i connect pinis', indonesian: 'Terhubung', - nepali: 'जडान भयो' + nepali: 'जडान भयो', + hindi: 'कनेक्ट हो गया', + burmese: 'ချိတ်ဆက်ပြီးပါပြီ', + thai: 'เชื่อมต่อแล้ว', + mandarin: '已连接' }, downloadStatus: { english: 'Download Status', @@ -6650,7 +9976,11 @@ export const localizations = { brazilian_portuguese: 'Status de Download', tok_pisin: 'Download Status', indonesian: 'Status Unduhan', - nepali: 'डाउनलोड स्थिति' + nepali: 'डाउनलोड स्थिति', + hindi: 'डाउनलोड स्थिति', + burmese: 'ဒေါင်းလုဒ် အခြေအနေ', + thai: 'สถานะการดาวน์โหลด', + mandarin: '下载状态' }, powersyncStatus: { english: 'PowerSync Status', @@ -6658,7 +9988,11 @@ export const localizations = { brazilian_portuguese: 'Status do PowerSync', tok_pisin: 'PowerSync Status', indonesian: 'Status PowerSync', - nepali: 'PowerSync स्थिति' + nepali: 'PowerSync स्थिति', + hindi: 'PowerSync स्थिति', + burmese: 'PowerSync အခြေအနေ', + thai: 'สถานะ PowerSync', + mandarin: 'PowerSync 状态' }, networkStatus: { english: 'Network Status', @@ -6666,7 +10000,11 @@ export const localizations = { brazilian_portuguese: 'Status da Rede', tok_pisin: 'Network Status', indonesian: 'Status Jaringan', - nepali: 'नेटवर्क स्थिति' + nepali: 'नेटवर्क स्थिति', + hindi: 'नेटवर्क स्थिति', + burmese: 'အင်တာနက် အခြေအနေ', + thai: 'สถานะเครือข่าย', + mandarin: '网络状态' }, attachmentDownloadProgress: { english: 'Attachment Download Progress', @@ -6674,7 +10012,11 @@ export const localizations = { brazilian_portuguese: 'Progresso de Download de Anexos', tok_pisin: 'Attachment Download Progress', indonesian: 'Kemajuan Unduhan Lampiran', - nepali: 'संलग्नक डाउनलोड प्रगति' + nepali: 'संलग्नक डाउनलोड प्रगति', + hindi: 'संलग्नक डाउनलोड प्रगति', + burmese: 'ပူးတွဲဖိုင် ဒေါင်းလုဒ် တိုးတက်မှု', + thai: 'ความคืบหน้าการดาวน์โหลดไฟล์แนบ', + mandarin: '附件下载进度' }, overallProgress: { english: 'Overall Progress', @@ -6682,7 +10024,11 @@ export const localizations = { brazilian_portuguese: 'Progresso Geral', tok_pisin: 'Overall Progress', indonesian: 'Kemajuan Keseluruhan', - nepali: 'समग्र प्रगति' + nepali: 'समग्र प्रगति', + hindi: 'कुल प्रगति', + burmese: 'စုစုပေါင်း တိုးတက်မှု', + thai: 'ความคืบหน้ารวม', + mandarin: '总体进度' }, currentDownload: { english: 'Current Download', @@ -6690,7 +10036,11 @@ export const localizations = { brazilian_portuguese: 'Download Atual', tok_pisin: 'Current Download', indonesian: 'Unduhan Saat Ini', - nepali: 'हालको डाउनलोड' + nepali: 'हालको डाउनलोड', + hindi: 'वर्तमान डाउनलोड', + burmese: 'လက်ရှိ ဒေါင်းလုဒ်', + thai: 'การดาวน์โหลดปัจจุบัน', + mandarin: '当前下载' }, currentUpload: { english: 'Current Upload', @@ -6698,7 +10048,11 @@ export const localizations = { brazilian_portuguese: 'Upload Atual', tok_pisin: 'Current Upload', indonesian: 'Unggahan Saat Ini', - nepali: 'हालको अपलोड' + nepali: 'हालको अपलोड', + hindi: 'वर्तमान अपलोड', + burmese: 'လက်ရှိ အပ်လုဒ်', + thai: 'การอัปโหลดปัจจุบัน', + mandarin: '当前上传' }, queueStatus: { english: 'Queue Status', @@ -6706,7 +10060,11 @@ export const localizations = { brazilian_portuguese: 'Status da Fila', tok_pisin: 'Queue Status', indonesian: 'Status Antrian', - nepali: 'लाम स्थिति' + nepali: 'लाम स्थिति', + hindi: 'कतार स्थिति', + burmese: 'တန်းစီမှု အခြေအနေ', + thai: 'สถานะคิว', + mandarin: '队列状态' }, allSynced: { english: 'All files synced', @@ -6714,7 +10072,11 @@ export const localizations = { brazilian_portuguese: 'Todos os arquivos sincronizados', tok_pisin: 'Olgeta file i sync pinis', indonesian: 'Semua file disinkronkan', - nepali: 'सबै फाइलहरू सिङ्क भयो' + nepali: 'सबै फाइलहरू सिङ्क भयो', + hindi: 'सभी फाइलें सिंक हो गईं', + burmese: 'ဖိုင်အားလုံး ထပ်တူပြုပြီးပါပြီ', + thai: 'ไฟล์ทั้งหมดซิงค์แล้ว', + mandarin: '所有文件已同步' }, signInToViewDownloadStatus: { english: 'Please sign in to view download status and sync information.', @@ -6725,7 +10087,12 @@ export const localizations = { tok_pisin: 'Plis sign in long lukim download status na sync info.', indonesian: 'Silakan masuk untuk melihat status unduhan dan informasi sinkronisasi.', - nepali: 'कृपया डाउनलोड स्थिति र सिंक जानकारी हेर्न साइन इन गर्नुहोस्।' + nepali: 'कृपया डाउनलोड स्थिति र सिंक जानकारी हेर्न साइन इन गर्नुहोस्।', + hindi: 'कृपया डाउनलोड स्थिति और सिंक जानकारी देखने के लिए साइन इन करें।', + burmese: + 'ဒေါင်းလုဒ် အခြေအနေနှင့် ထပ်တူပြုခြင်း အချက်အလက်များကို ကြည့်ရှုရန် ကျေးဇူးပြု၍ ဝင်ရောက်ပါ။', + thai: 'กรุณาเข้าสู่ระบบเพื่อดูสถานะการดาวน์โหลดและข้อมูลการซิงค์', + mandarin: '请登录以查看下载状态和同步信息。' }, unsynced: { english: 'Unsynced', @@ -6733,7 +10100,11 @@ export const localizations = { brazilian_portuguese: 'Não sincronizado', tok_pisin: 'i no sync yet', indonesian: 'Tidak disinkronkan', - nepali: 'सिङ्क भएको छैन' + nepali: 'सिङ्क भएको छैन', + hindi: 'सिंक नहीं हुआ', + burmese: 'ထပ်တူမပြုရသေး', + thai: 'ยังไม่ได้ซิงค์', + mandarin: '未同步' }, onboardingCreateProjectTitle: { english: 'Record a Bible, or any other content', @@ -6741,7 +10112,11 @@ export const localizations = { brazilian_portuguese: 'Grave uma Bíblia ou qualquer outro conteúdo', tok_pisin: 'Rekodim Baibel o ol narapela samting', indonesian: 'Rekam Alkitab atau konten lainnya', - nepali: 'बाइबल वा अन्य कुनै पनि सामग्री रेकर्ड गर्नुहोस्' + nepali: 'बाइबल वा अन्य कुनै पनि सामग्री रेकर्ड गर्नुहोस्', + hindi: 'बाइबल या कोई अन्य सामग्री रिकॉर्ड करें', + burmese: 'ကျမ်းစာ သို့မဟုတ် အခြားမည်သည့် အကြောင်းအရာကိုမဆို မှတ်တမ်းတင်ပါ', + thai: 'บันทึกพระคัมภีร์หรือเนื้อหาอื่นๆ', + mandarin: '录制圣经或任何其他内容' }, onboardingCreateProjectSubtitle: { english: 'Start by creating your first project', @@ -6749,7 +10124,11 @@ export const localizations = { brazilian_portuguese: 'Comece criando seu primeiro projeto', tok_pisin: 'Stat long mekim nupela projek', indonesian: 'Mulai dengan membuat proyek pertama Anda', - nepali: 'आफ्नो पहिलो प्रोजेक्ट सिर्जना गरेर सुरु गर्नुहोस्' + nepali: 'आफ्नो पहिलो प्रोजेक्ट सिर्जना गरेर सुरु गर्नुहोस्', + hindi: 'अपना पहला प्रोजेक्ट बनाकर शुरू करें', + burmese: 'သင်၏ ပထမဆုံး ပရောဂျက်ကို ဖန်တီးခြင်းဖြင့် စတင်ပါ', + thai: 'เริ่มต้นด้วยการสร้างโปรเจกต์แรกของคุณ', + mandarin: '通过创建您的第一个项目开始' }, onboardingCreateProjectExample: { english: 'Stories', @@ -6757,7 +10136,11 @@ export const localizations = { brazilian_portuguese: 'Histórias', tok_pisin: 'Stori', indonesian: 'Cerita', - nepali: 'कथाहरू' + nepali: 'कथाहरू', + hindi: 'कहानियाँ', + burmese: 'ပုံပြင်များ', + thai: 'เรื่องราว', + mandarin: '故事' }, onboardingCreateProjectDescription: { english: 'Example project name', @@ -6765,7 +10148,11 @@ export const localizations = { brazilian_portuguese: 'Nome do projeto de exemplo', tok_pisin: 'Nem bilong projek olsem', indonesian: 'Nama proyek contoh', - nepali: 'उदाहरण प्रोजेक्ट नाम' + nepali: 'उदाहरण प्रोजेक्ट नाम', + hindi: 'उदाहरण प्रोजेक्ट नाम', + burmese: 'ဥပမာ ပရောဂျက် အမည်', + thai: 'ชื่อโปรเจกต์ตัวอย่าง', + mandarin: '示例项目名称' }, onboardingCreateProject: { english: 'Create Project', @@ -6773,7 +10160,11 @@ export const localizations = { brazilian_portuguese: 'Criar Projeto', tok_pisin: 'Mekim Projek', indonesian: 'Buat Proyek', - nepali: 'प्रोजेक्ट सिर्जना गर्नुहोस्' + nepali: 'प्रोजेक्ट सिर्जना गर्नुहोस्', + hindi: 'प्रोजेक्ट बनाएं', + burmese: 'ပရောဂျက် ဖန်တီးပါ', + thai: 'สร้างโปรเจกต์', + mandarin: '创建项目' }, onboardingCreateQuestTitle: { english: 'Organize your content', @@ -6781,7 +10172,11 @@ export const localizations = { brazilian_portuguese: 'Organize seu conteúdo', tok_pisin: 'Oganaisim samting bilong yu', indonesian: 'Organisir konten Anda', - nepali: 'आफ्नो सामग्री व्यवस्थित गर्नुहोस्' + nepali: 'आफ्नो सामग्री व्यवस्थित गर्नुहोस्', + hindi: 'अपनी सामग्री व्यवस्थित करें', + burmese: 'သင်၏ အကြောင်းအရာကို စီစဉ်ပါ', + thai: 'จัดระเบียบเนื้อหาของคุณ', + mandarin: '整理您的内容' }, onboardingCreateQuestSubtitle: { english: 'Add quests to break down your project into manageable pieces', @@ -6792,7 +10187,13 @@ export const localizations = { indonesian: 'Tambahkan quest untuk membagi proyek Anda menjadi bagian yang dapat dikelola', nepali: - 'आफ्नो प्रोजेक्टलाई व्यवस्थापन योग्य टुक्राहरूमा विभाजन गर्न क्वेस्टहरू थप्नुहोस्' + 'आफ्नो प्रोजेक्टलाई व्यवस्थापन योग्य टुक्राहरूमा विभाजन गर्न क्वेस्टहरू थप्नुहोस्', + hindi: + 'अपने प्रोजेक्ट को प्रबंधनीय टुकड़ों में विभाजित करने के लिए क्वेस्ट जोड़ें', + burmese: + 'သင်၏ ပရောဂျက်ကို စီမံခန့်ခွဲနိုင်သော အပိုင်းများအဖြစ် ခွဲရန် quest များ ထည့်ပါ', + thai: 'เพิ่มเควสต์เพื่อแบ่งโปรเจกต์ของคุณออกเป็นส่วนที่จัดการได้', + mandarin: '添加任务以将您的项目分解为可管理的部分' }, onboardingQuestExample1: { english: 'Story 1', @@ -6800,7 +10201,11 @@ export const localizations = { brazilian_portuguese: 'História 1', tok_pisin: 'Stori 1', indonesian: 'Cerita 1', - nepali: 'कथा १' + nepali: 'कथा १', + hindi: 'कहानी 1', + burmese: 'ပုံပြင် ၁', + thai: 'เรื่องราว 1', + mandarin: '故事 1' }, onboardingQuestExample2: { english: 'Story 2', @@ -6808,7 +10213,11 @@ export const localizations = { brazilian_portuguese: 'História 2', tok_pisin: 'Stori 2', indonesian: 'Cerita 2', - nepali: 'कथा २' + nepali: 'कथा २', + hindi: 'कहानी 2', + burmese: 'ပုံပြင် ၂', + thai: 'เรื่องราว 2', + mandarin: '故事 2' }, onboardingCreateQuest: { english: 'Create Quest', @@ -6816,7 +10225,11 @@ export const localizations = { brazilian_portuguese: 'Criar Missão', tok_pisin: 'Mekim Kwest', indonesian: 'Buat Quest', - nepali: 'क्वेस्ट सिर्जना गर्नुहोस्' + nepali: 'क्वेस्ट सिर्जना गर्नुहोस्', + hindi: 'क्वेस्ट बनाएं', + burmese: 'Quest ဖန်တီးပါ', + thai: 'สร้างเควสต์', + mandarin: '创建任务' }, onboardingRecordAudioTitle: { english: 'Start recording', @@ -6824,7 +10237,11 @@ export const localizations = { brazilian_portuguese: 'Comece a gravar', tok_pisin: 'Stat long rekodim', indonesian: 'Mulai merekam', - nepali: 'रेकर्डिङ सुरु गर्नुहोस्' + nepali: 'रेकर्डिङ सुरु गर्नुहोस्', + hindi: 'रिकॉर्डिंग शुरू करें', + burmese: 'မှတ်တမ်းတင်ခြင်း စတင်ပါ', + thai: 'เริ่มบันทึก', + mandarin: '开始录制' }, onboardingRecordAudioSubtitle: { english: 'Hold the button to record, or slide to record anytime you talk', @@ -6837,7 +10254,13 @@ export const localizations = { indonesian: 'Tahan tombol untuk merekam, atau geser untuk merekam kapan saja Anda berbicara', nepali: - 'रेकर्ड गर्न बटन थिच्नुहोस्, वा तपाईं बोल्दा जुनसुकै बेला रेकर्ड गर्न स्लाइड गर्नुहोस्' + 'रेकर्ड गर्न बटन थिच्नुहोस्, वा तपाईं बोल्दा जुनसुकै बेला रेकर्ड गर्न स्लाइड गर्नुहोस्', + hindi: + 'रिकॉर्ड करने के लिए बटन दबाए रखें, या जब भी आप बोलें तो रिकॉर्ड करने के लिए स्लाइड करें', + burmese: + 'မှတ်တမ်းတင်ရန် ခလုတ်ကို ကိုင်ထားပါ သို့မဟုတ် သင်စကားပြောသောအခါ မှတ်တမ်းတင်ရန် ရွှေ့ပါ', + thai: 'กดปุ่มค้างไว้เพื่อบันทึก หรือเลื่อนเพื่อบันทึกเมื่อใดก็ตามที่คุณพูด', + mandarin: '按住按钮录制,或滑动以在您说话时随时录制' }, onboardingRecordMethod1: { english: 'Hold button to record', @@ -6845,7 +10268,11 @@ export const localizations = { brazilian_portuguese: 'Mantenha pressionado para gravar', tok_pisin: 'Holim button long rekodim', indonesian: 'Tahan tombol untuk merekam', - nepali: 'रेकर्ड गर्न बटन थिच्नुहोस्' + nepali: 'रेकर्ड गर्न बटन थिच्नुहोस्', + hindi: 'रिकॉर्ड करने के लिए बटन दबाए रखें', + burmese: 'မှတ်တမ်းတင်ရန် ခလုတ်ကို ကိုင်ထားပါ', + thai: 'กดปุ่มค้างไว้เพื่อบันทึก', + mandarin: '按住按钮录制' }, onboardingRecordMethod2: { english: 'Slide to record anytime you talk', @@ -6853,7 +10280,11 @@ export const localizations = { brazilian_portuguese: 'Deslize para gravar quando falar', tok_pisin: 'Slipim long rekodim taim yu toktok', indonesian: 'Geser untuk merekam kapan saja Anda berbicara', - nepali: 'बोल्दा जुनसुकै बेला रेकर्ड गर्न स्लाइड गर्नुहोस्' + nepali: 'बोल्दा जुनसुकै बेला रेकर्ड गर्न स्लाइड गर्नुहोस्', + hindi: 'जब भी आप बोलें तो रिकॉर्ड करने के लिए स्लाइड करें', + burmese: 'သင်စကားပြောသောအခါ မှတ်တမ်းတင်ရန် ရွှေ့ပါ', + thai: 'เลื่อนเพื่อบันทึกเมื่อใดก็ตามที่คุณพูด', + mandarin: '滑动以在您说话时随时录制' }, onboardingStartRecording: { english: 'Start Recording', @@ -6861,7 +10292,11 @@ export const localizations = { brazilian_portuguese: 'Iniciar Gravação', tok_pisin: 'Stat Rekodim', indonesian: 'Mulai Merekam', - nepali: 'रेकर्डिङ सुरु गर्नुहोस्' + nepali: 'रेकर्डिङ सुरु गर्नुहोस्', + hindi: 'रिकॉर्डिंग शुरू करें', + burmese: 'မှတ်တမ်းတင်ခြင်း စတင်ပါ', + thai: 'เริ่มบันทึก', + mandarin: '开始录制' }, onboardingInviteTitle: { english: 'Work together', @@ -6869,7 +10304,11 @@ export const localizations = { brazilian_portuguese: 'Trabalhe juntos', tok_pisin: 'Wok wantaim', indonesian: 'Bekerja bersama', - nepali: 'सँगै काम गर्नुहोस्' + nepali: 'सँगै काम गर्नुहोस्', + hindi: 'साथ में काम करें', + burmese: 'အတူတကွ လုပ်ဆောင်ပါ', + thai: 'ทำงานร่วมกัน', + mandarin: '一起工作' }, onboardingInviteSubtitle: { english: @@ -6883,7 +10322,13 @@ export const localizations = { indonesian: 'Undang orang lain untuk berkolaborasi. Mereka akan menerima notifikasi dan melihat proyek Anda di daftar mereka', nepali: - 'अरूलाई सहयोग गर्न निम्तो दिनुहोस्। उनीहरूले सूचना प्राप्त गर्नेछन् र उनीहरूको सूचीमा तपाईंको प्रोजेक्ट देख्नेछन्' + 'अरूलाई सहयोग गर्न निम्तो दिनुहोस्। उनीहरूले सूचना प्राप्त गर्नेछन् र उनीहरूको सूचीमा तपाईंको प्रोजेक्ट देख्नेछन्', + hindi: + 'दूसरों को सहयोग करने के लिए आमंत्रित करें। उन्हें एक सूचना मिलेगी और वे अपनी सूची में आपका प्रोजेक्ट देखेंगे', + burmese: + 'အခြားသူများကို ပူးပေါင်းလုပ်ဆောင်ရန် ဖိတ်ခေါ်ပါ။ သူတို့သည် အကြောင်းကြားချက်တစ်ခု ရရှိမည်ဖြစ်ပြီး သူတို့၏ စာရင်းတွင် သင်၏ ပရောဂျက်ကို မြင်ရမည်', + thai: 'เชิญผู้อื่นให้ร่วมมือกัน พวกเขาจะได้รับการแจ้งเตือนและเห็นโปรเจกต์ของคุณในรายการของพวกเขา', + mandarin: '邀请他人协作。他们将收到通知并在他们的列表中看到您的项目' }, onboardingInviteBenefit1: { english: 'They receive a notification', @@ -6891,7 +10336,11 @@ export const localizations = { brazilian_portuguese: 'Eles recebem uma notificação', tok_pisin: 'Ol kisim notis', indonesian: 'Mereka menerima notifikasi', - nepali: 'उनीहरूले सूचना प्राप्त गर्छन्' + nepali: 'उनीहरूले सूचना प्राप्त गर्छन्', + hindi: 'उन्हें एक सूचना मिलती है', + burmese: 'သူတို့သည် အကြောင်းကြားချက်တစ်ခု ရရှိသည်', + thai: 'พวกเขาได้รับการแจ้งเตือน', + mandarin: '他们会收到通知' }, onboardingInviteBenefit2: { english: 'Project appears in their list', @@ -6899,7 +10348,11 @@ export const localizations = { brazilian_portuguese: 'O projeto aparece em sua lista', tok_pisin: 'Projek i kamap long list bilong ol', indonesian: 'Proyek muncul di daftar mereka', - nepali: 'प्रोजेक्ट उनीहरूको सूचीमा देखिन्छ' + nepali: 'प्रोजेक्ट उनीहरूको सूचीमा देखिन्छ', + hindi: 'प्रोजेक्ट उनकी सूची में दिखाई देता है', + burmese: 'ပရောဂျက်သည် သူတို့၏ စာရင်းတွင် ပေါ်လာသည်', + thai: 'โปรเจกต์ปรากฏในรายการของพวกเขา', + mandarin: '项目出现在他们的列表中' }, onboardingInviteCollaborators: { english: 'Invite Collaborators', @@ -6907,7 +10360,11 @@ export const localizations = { brazilian_portuguese: 'Convidar Colaboradores', tok_pisin: 'Singim Ol Wokman', indonesian: 'Undang Kolaborator', - nepali: 'सहकर्मीहरूलाई निम्तो दिनुहोस्' + nepali: 'सहकर्मीहरूलाई निम्तो दिनुहोस्', + hindi: 'सहयोगियों को आमंत्रित करें', + burmese: 'ပူးပေါင်းလုပ်ဆောင်သူများကို ဖိတ်ခေါ်ပါ', + thai: 'เชิญผู้ร่วมงาน', + mandarin: '邀请协作者' }, onboardingContinue: { english: 'Continue', @@ -6915,7 +10372,11 @@ export const localizations = { brazilian_portuguese: 'Continuar', tok_pisin: 'Gohet', indonesian: 'Lanjutkan', - nepali: 'जारी राख्नुहोस्' + nepali: 'जारी राख्नुहोस्', + hindi: 'जारी रखें', + burmese: 'ဆက်လုပ်ပါ', + thai: 'ดำเนินการต่อ', + mandarin: '继续' }, onboardingBible: { english: 'Bible', @@ -6923,7 +10384,11 @@ export const localizations = { brazilian_portuguese: 'Bíblia', tok_pisin: 'Baibel', indonesian: 'Alkitab', - nepali: 'बाइबल' + nepali: 'बाइबल', + hindi: 'बाइबल', + burmese: 'ကျမ်းစာ', + thai: 'พระคัมภีร์', + mandarin: '圣经' }, onboardingOther: { english: 'Other', @@ -6931,7 +10396,11 @@ export const localizations = { brazilian_portuguese: 'Outro', tok_pisin: 'Narapela', indonesian: 'Lainnya', - nepali: 'अन्य' + nepali: 'अन्य', + hindi: 'अन्य', + burmese: 'အခြား', + thai: 'อื่นๆ', + mandarin: '其他' }, onboardingBibleSelectBookTitle: { english: 'Select a Book', @@ -6939,7 +10408,11 @@ export const localizations = { brazilian_portuguese: 'Selecione um Livro', tok_pisin: 'Pilim Buk', indonesian: 'Pilih Buku', - nepali: 'एउटा पुस्तक चयन गर्नुहोस्' + nepali: 'एउटा पुस्तक चयन गर्नुहोस्', + hindi: 'एक पुस्तक चुनें', + burmese: 'စာအုပ်တစ်အုပ်ကို ရွေးပါ', + thai: 'เลือกหนังสือ', + mandarin: '选择一本书' }, onboardingBibleSelectBookSubtitle: { english: 'Choose which book of the Bible to translate', @@ -6947,7 +10420,11 @@ export const localizations = { brazilian_portuguese: 'Escolha qual livro da Bíblia traduzir', tok_pisin: 'Pilim wanpela buk bilong Baibel long tanim', indonesian: 'Pilih buku Alkitab mana yang akan diterjemahkan', - nepali: 'बाइबलको कुन पुस्तक अनुवाद गर्ने छान्नुहोस्' + nepali: 'बाइबलको कुन पुस्तक अनुवाद गर्ने छान्नुहोस्', + hindi: 'चुनें कि बाइबल की कौन सी पुस्तक का अनुवाद करना है', + burmese: 'ကျမ်းစာ၏ မည်သည့် စာအုပ်ကို ဘာသာပြန်ရမည်ကို ရွေးပါ', + thai: 'เลือกหนังสือเล่มไหนของพระคัมภีร์ที่จะแปล', + mandarin: '选择要翻译的圣经书籍' }, onboardingBibleBookExample1: { english: 'Genesis', @@ -6955,7 +10432,11 @@ export const localizations = { brazilian_portuguese: 'Gênesis', tok_pisin: 'Jenesis', indonesian: 'Kejadian', - nepali: 'उत्पत्ति' + nepali: 'उत्पत्ति', + hindi: 'उत्पत्ति', + burmese: 'ကာလအစ', + thai: 'ปฐมกาล', + mandarin: '创世记' }, onboardingBibleBookExample2: { english: 'Matthew', @@ -6963,7 +10444,11 @@ export const localizations = { brazilian_portuguese: 'Mateus', tok_pisin: 'Matyu', indonesian: 'Matius', - nepali: 'मत्ती' + nepali: 'मत्ती', + hindi: 'मत्ती', + burmese: 'မဿဲ', + thai: 'มัทธิว', + mandarin: '马太福音' }, onboardingBibleCreateChapterTitle: { english: 'Create Chapter Quests', @@ -6971,7 +10456,11 @@ export const localizations = { brazilian_portuguese: 'Criar Quests de Capítulos', tok_pisin: 'Mekim Ol Kwest bilong Kapitol', indonesian: 'Buat Quest Bab', - nepali: 'अध्याय क्वेस्टहरू सिर्जना गर्नुहोस्' + nepali: 'अध्याय क्वेस्टहरू सिर्जना गर्नुहोस्', + hindi: 'अध्याय क्वेस्ट बनाएं', + burmese: 'အခန်း Quest များ ဖန်တီးပါ', + thai: 'สร้างเควสต์บท', + mandarin: '创建章节任务' }, onboardingBibleCreateChapterSubtitle: { english: 'Each chapter becomes a quest you can work on', @@ -6981,7 +10470,11 @@ export const localizations = { 'Cada capítulo se torna uma quest em que você pode trabalhar', tok_pisin: 'Olgeta kapitol i kamap wanpela kwest yu ken wok long en', indonesian: 'Setiap bab menjadi quest yang dapat Anda kerjakan', - nepali: 'प्रत्येक अध्याय एउटा क्वेस्ट बन्छ जसमा तपाईं काम गर्न सक्नुहुन्छ' + nepali: 'प्रत्येक अध्याय एउटा क्वेस्ट बन्छ जसमा तपाईं काम गर्न सक्नुहुन्छ', + hindi: 'प्रत्येक अध्याय एक क्वेस्ट बन जाता है जिस पर आप काम कर सकते हैं', + burmese: 'အခန်းတစ်ခန်းစီသည် သင်လုပ်ဆောင်နိုင်သော quest တစ်ခု ဖြစ်လာသည်', + thai: 'แต่ละบทจะกลายเป็นเควสต์ที่คุณสามารถทำงานได้', + mandarin: '每一章都成为一个您可以处理的任务' }, onboardingBibleChapterExample1: { english: 'Chapter 1', @@ -6989,7 +10482,11 @@ export const localizations = { brazilian_portuguese: 'Capítulo 1', tok_pisin: 'Kapitol 1', indonesian: 'Bab 1', - nepali: 'अध्याय १' + nepali: 'अध्याय १', + hindi: 'अध्याय 1', + burmese: 'အခန်း ၁', + thai: 'บทที่ 1', + mandarin: '第1章' }, onboardingBibleChapterExample2: { english: 'Chapter 2', @@ -6997,7 +10494,11 @@ export const localizations = { brazilian_portuguese: 'Capítulo 2', tok_pisin: 'Kapitol 2', indonesian: 'Bab 2', - nepali: 'अध्याय २' + nepali: 'अध्याय २', + hindi: 'अध्याय 2', + burmese: 'အခန်း ၂', + thai: 'บทที่ 2', + mandarin: '第2章' }, onboardingVisionTitle: { english: 'Every language. Every culture.', @@ -7005,7 +10506,11 @@ export const localizations = { brazilian_portuguese: 'Cada idioma. Cada cultura.', tok_pisin: 'Olgeta tokples. Olgeta kalsa.', indonesian: 'Setiap bahasa. Setiap budaya.', - nepali: 'प्रत्येक भाषा। प्रत्येक संस्कृति।' + nepali: 'प्रत्येक भाषा। प्रत्येक संस्कृति।', + hindi: 'हर भाषा। हर संस्कृति।', + burmese: 'ဘာသာစကားတိုင်း။ ယဉ်ကျေးမှုတိုင်း။', + thai: 'ทุกภาษา ทุกวัฒนธรรม', + mandarin: '每种语言。每种文化。' }, onboardingVisionSubtitle: { english: @@ -7019,7 +10524,14 @@ export const localizations = { indonesian: 'Kumpulkan data bahasa teks dan audio dengan cepat. Lokal pertama, sinkronkan saat terhubung. Berkolaborasi, terjemahkan, validasi.', nepali: - 'पाठ र अडियो भाषा डाटा छिट्टै सङ्कलन गर्नुहोस्। स्थानीय-प्रथम, जडान हुँदा सिंक गर्नुहोस्। सहयोग गर्नुहोस्, अनुवाद गर्नुहोस्, प्रमाणित गर्नुहोस्।' + 'पाठ र अडियो भाषा डाटा छिट्टै सङ्कलन गर्नुहोस्। स्थानीय-प्रथम, जडान हुँदा सिंक गर्नुहोस्। सहयोग गर्नुहोस्, अनुवाद गर्नुहोस्, प्रमाणित गर्नुहोस्।', + hindi: + 'पाठ और ऑडियो भाषा डेटा जल्दी से एकत्र करें। स्थानीय-प्रथम, कनेक्ट होने पर सिंक करें। सहयोग करें, अनुवाद करें, मान्य करें।', + burmese: + 'စာသားနှင့် အသံ ဘာသာစကား ဒေတာများကို အမြန်စုဆောင်းပါ။ ဒေသတွင်း-ဦးစားပေး၊ ချိတ်ဆက်သောအခါ ထပ်တူပြုပါ။ ပူးပေါင်းလုပ်ဆောင်ပါ၊ ဘာသာပြန်ပါ၊ အတည်ပြုပါ။', + thai: 'รวบรวมข้อมูลภาษาข้อความและเสียงอย่างรวดเร็ว เน้นท้องถิ่นก่อน ซิงค์เมื่อเชื่อมต่อ ร่วมมือ แปล และตรวจสอบ', + mandarin: + '快速收集文本和音频语言数据。本地优先,连接时同步。协作、翻译、验证。' }, onboardingVisionStatement1: { english: "Every language having access to the world's knowledge.", @@ -7027,7 +10539,11 @@ export const localizations = { brazilian_portuguese: 'Cada idioma tendo acesso ao conhecimento do mundo.', tok_pisin: 'Olgeta tokples i gat akses long save bilong wol.', indonesian: 'Setiap bahasa memiliki akses ke pengetahuan dunia.', - nepali: 'प्रत्येक भाषाले विश्वको ज्ञानमा पहुँच पाउने।' + nepali: 'प्रत्येक भाषाले विश्वको ज्ञानमा पहुँच पाउने।', + hindi: 'हर भाषा को दुनिया के ज्ञान तक पहुंच हो।', + burmese: 'ဘာသာစကားတိုင်းသည် ကမ္ဘာ၏ အသိပညာသို့ ရောက်ရှိနိုင်သည်။', + thai: 'ทุกภาษามีการเข้าถึงความรู้ของโลก', + mandarin: '每种语言都能获得世界知识。' }, onboardingVisionStatement2: { english: 'Every culture sharing its meaning with the world.', @@ -7036,7 +10552,11 @@ export const localizations = { 'Cada cultura compartilhando seu significado com o mundo.', tok_pisin: 'Olgeta kalsa i salim save bilong en i go long wol.', indonesian: 'Setiap budaya berbagi maknanya dengan dunia.', - nepali: 'प्रत्येक संस्कृतिले आफ्नो अर्थ विश्वसँग साझा गर्ने।' + nepali: 'प्रत्येक संस्कृतिले आफ्नो अर्थ विश्वसँग साझा गर्ने।', + hindi: 'हर संस्कृति अपना अर्थ दुनिया के साथ साझा कर रही है।', + burmese: 'ယဉ်ကျေးမှုတိုင်းသည် ၎င်း၏ အဓိပ္ပာယ်ကို ကမ္ဘာနှင့် မျှဝေနေသည်။', + thai: 'ทุกวัฒนธรรมแบ่งปันความหมายกับโลก', + mandarin: '每种文化都在与世界分享其意义。' }, onboardingVisionCC0: { english: 'CC0/public domain data ensures no party can stop this vision.', @@ -7049,7 +10569,13 @@ export const localizations = { indonesian: 'Data CC0/domain publik memastikan tidak ada pihak yang dapat menghentikan visi ini.', nepali: - 'CC0/सार्वजनिक डोमेन डाटाले कुनै पनि पक्षले यो दृष्टिकोणलाई रोक्न नसक्ने सुनिश्चित गर्छ।' + 'CC0/सार्वजनिक डोमेन डाटाले कुनै पनि पक्षले यो दृष्टिकोणलाई रोक्न नसक्ने सुनिश्चित गर्छ।', + hindi: + 'CC0/सार्वजनिक डोमेन डेटा सुनिश्चित करता है कि कोई भी पक्ष इस दृष्टि को रोक नहीं सकता।', + burmese: + 'CC0/ပြည်သူ့ဒိုမိန်း ဒေတာသည် မည်သည့်အဖွဲ့မျှ ဤမျှော်မှန်းချက်ကို ရပ်တန့်နိုင်မည်မဟုတ်ကြောင်း သေချာစေသည်။', + thai: 'ข้อมูล CC0/สาธารณสมบัติรับประกันว่าไม่มีฝ่ายใดสามารถหยุดวิสัยทัศน์นี้ได้', + mandarin: 'CC0/公共领域数据确保没有任何一方可以阻止这一愿景。' }, onboardingOurVision: { english: 'Our Vision', @@ -7057,7 +10583,11 @@ export const localizations = { brazilian_portuguese: 'Nossa Visão', tok_pisin: 'Visen Bilong Mipela', indonesian: 'Visi Kami', - nepali: 'हाम्रो दृष्टि' + nepali: 'हाम्रो दृष्टि', + hindi: 'हमारी दृष्टि', + burmese: 'ကျွန်ုပ်တို့၏ မျှော်မှန်းချက်', + thai: 'วิสัยทัศน์ของเรา', + mandarin: '我们的愿景' }, onboardingSelectLanguageTitle: { english: 'Choose Your Language', @@ -7065,7 +10595,11 @@ export const localizations = { brazilian_portuguese: 'Escolha Seu Idioma', tok_pisin: 'Pilim Tokples Bilong Yu', indonesian: 'Pilih Bahasa Anda', - nepali: 'आफ्नो भाषा छान्नुहोस्' + nepali: 'आफ्नो भाषा छान्नुहोस्', + hindi: 'अपनी भाषा चुनें', + burmese: 'သင်၏ ဘာသာစကားကို ရွေးပါ', + thai: 'เลือกภาษาของคุณ', + mandarin: '选择您的语言' }, onboardingSelectLanguageSubtitle: { english: "Select the language you'd like to use for the app interface", @@ -7075,7 +10609,11 @@ export const localizations = { 'Selecione o idioma que deseja usar para a interface do aplicativo', tok_pisin: 'Pilim tokples yu laikim long yusim long app', indonesian: 'Pilih bahasa yang ingin Anda gunakan untuk antarmuka aplikasi', - nepali: 'एप इन्टरफेसको लागि तपाईं प्रयोग गर्न चाहनुहुने भाषा चयन गर्नुहोस्' + nepali: 'एप इन्टरफेसको लागि तपाईं प्रयोग गर्न चाहनुहुने भाषा चयन गर्नुहोस्', + hindi: 'ऐप इंटरफेस के लिए आप जिस भाषा का उपयोग करना चाहते हैं उसे चुनें', + burmese: 'အက်ပ်၏ အင်တာဖေ့စ်အတွက် အသုံးပြုလိုသော ဘာသာစကားကို ရွေးပါ', + thai: 'เลือกภาษาที่คุณต้องการใช้สำหรับอินเทอร์เฟซแอป', + mandarin: '选择您希望用于应用界面的语言' }, exportProgress: { english: 'Export Progress', @@ -7083,7 +10621,11 @@ export const localizations = { brazilian_portuguese: 'Progresso de Exportação', tok_pisin: 'Export Progress', indonesian: 'Progres Exportasi', - nepali: 'निर्यात प्रगति' + nepali: 'निर्यात प्रगति', + hindi: 'निर्यात प्रगति', + burmese: 'တင်ပို့မှု တိုးတက်မှု', + thai: 'ความคืบหน้าการส่งออก', + mandarin: '导出进度' }, exporting: { english: 'Exporting chapter... This may take a few moments.', @@ -7092,7 +10634,11 @@ export const localizations = { 'Exportando capítulo... Isso pode levar alguns momentos.', tok_pisin: 'Exporting chapter... This may take a few moments.', indonesian: 'Mengekspor bab... Ini mungkin memakan beberapa saat.', - nepali: 'अध्याय निर्यात गर्दै... यसले केही क्षण लिन सक्छ।' + nepali: 'अध्याय निर्यात गर्दै... यसले केही क्षण लिन सक्छ।', + hindi: 'अध्याय निर्यात हो रहा है... इसमें कुछ क्षण लग सकते हैं।', + burmese: 'အခန်းကို တင်ပို့နေသည်... ခဏကြာနိုင်ပါသည်။', + thai: 'กำลังส่งออกบท... อาจใช้เวลาสักครู่', + mandarin: '正在导出章节... 可能需要一些时间。' }, exportReady: { english: 'Export is ready!', @@ -7100,7 +10646,11 @@ export const localizations = { brazilian_portuguese: 'Exportação pronta!', tok_pisin: 'Export is ready!', indonesian: 'Ekspor siap!', - nepali: 'निर्यात तयार छ!' + nepali: 'निर्यात तयार छ!', + hindi: 'निर्यात तैयार है!', + burmese: 'တင်ပို့မှု အဆင်သင့်ပါပြီ!', + thai: 'การส่งออกพร้อมแล้ว!', + mandarin: '导出已就绪!' }, share: { english: 'Share', @@ -7108,7 +10658,11 @@ export const localizations = { brazilian_portuguese: 'Compartilhar', tok_pisin: 'Share', indonesian: 'Bagikan', - nepali: 'साझा गर्नुहोस्' + nepali: 'साझा गर्नुहोस्', + hindi: 'साझा करें', + burmese: 'မျှဝေပါ', + thai: 'แชร์', + mandarin: '分享' }, exportFailed: { english: 'Export failed', @@ -7116,7 +10670,11 @@ export const localizations = { brazilian_portuguese: 'Exportação falhou', tok_pisin: 'Export failed', indonesian: 'Ekspor gagal', - nepali: 'निर्यात असफल भयो' + nepali: 'निर्यात असफल भयो', + hindi: 'निर्यात विफल', + burmese: 'တင်ပို့မှု မအောင်မြင်ပါ', + thai: 'ส่งออกไม่สำเร็จ', + mandarin: '导出失败' }, close: { english: 'Close', @@ -7124,7 +10682,11 @@ export const localizations = { brazilian_portuguese: 'Fechar', tok_pisin: 'Close', indonesian: 'Tutup', - nepali: 'बन्द गर्नुहोस्' + nepali: 'बन्द गर्नुहोस्', + hindi: 'बंद करें', + burmese: 'ပိတ်ပါ', + thai: 'ปิด', + mandarin: '关闭' }, exportForDistribution: { english: 'Export for Distribution', @@ -7132,7 +10694,11 @@ export const localizations = { brazilian_portuguese: 'Exportar para distribuição', tok_pisin: 'Export for Distribution', indonesian: 'Ekspor untuk Distribusi', - nepali: 'वितरणको लागि निर्यात' + nepali: 'वितरणको लागि निर्यात', + hindi: 'वितरण के लिए निर्यात', + burmese: 'ဖြန့်ဖြူးရန် တင်ပို့ပါ', + thai: 'ส่งออกเพื่อการแจกจ่าย', + mandarin: '导出以供分发' }, exportForDistributionDescription: { english: 'This export is intended for public distribution and sharing.', @@ -7143,7 +10709,12 @@ export const localizations = { tok_pisin: 'Dispela export bilong wok long putim igo aut long olgeta na kisim sindaun wantaim ol arapela.', indonesian: 'Ekspor ini dimaksudkan untuk distribusi dan pembagian publik.', - nepali: 'यो निर्यात सार्वजनिक वितरण र साझेदारीको लागि हो।' + nepali: 'यो निर्यात सार्वजनिक वितरण र साझेदारीको लागि हो।', + hindi: 'यह निर्यात सार्वजनिक वितरण और साझाकरण के लिए है।', + burmese: + 'ဤတင်ပို့မှုသည် ပြည်သူ့ ဖြန့်ဖြူးမှုနှင့် မျှဝေမှုအတွက် ရည်ရွယ်သည်။', + thai: 'การส่งออกนี้มีไว้สำหรับการแจกจ่ายและแบ่งปันสาธารณะ', + mandarin: '此导出用于公共分发和共享。' }, exportForFeedback: { english: 'Export for Feedback', @@ -7151,7 +10722,11 @@ export const localizations = { brazilian_portuguese: 'Exportar para feedback', tok_pisin: 'Export for Feedback', indonesian: 'Ekspor untuk Feedback', - nepali: 'प्रतिक्रियाको लागि निर्यात' + nepali: 'प्रतिक्रियाको लागि निर्यात', + hindi: 'प्रतिक्रिया के लिए निर्यात', + burmese: 'အကြံပြုချက်အတွက် တင်ပို့ပါ', + thai: 'ส่งออกเพื่อรับข้อเสนอแนะ', + mandarin: '导出以供反馈' }, exportForFeedbackDescription: { english: 'This export is intended for feedback and sharing.', @@ -7160,7 +10735,11 @@ export const localizations = { 'Esta exportação é destinada a feedback e compartilhado.', tok_pisin: 'Dispela export bilong wok long feedback o share.', indonesian: 'Ekspor ini dimaksudkan untuk feedback dan pembagian.', - nepali: 'यो निर्यात प्रतिक्रिया र साझेदारीको लागि हो।' + nepali: 'यो निर्यात प्रतिक्रिया र साझेदारीको लागि हो।', + hindi: 'यह निर्यात प्रतिक्रिया और साझाकरण के लिए है।', + burmese: 'ဤတင်ပို့မှုသည် အကြံပြုချက်နှင့် မျှဝေမှုအတွက် ရည်ရွယ်သည်။', + thai: 'การส่งออกนี้มีไว้สำหรับรับข้อเสนอแนะและการแบ่งปัน', + mandarin: '此导出用于反馈和共享。' }, selectExportType: { english: 'Select Export Type', @@ -7168,7 +10747,11 @@ export const localizations = { brazilian_portuguese: 'Selecionar tipo de exportação', tok_pisin: 'Makim kain export', indonesian: 'Pilih Jenis Ekspor', - nepali: 'निर्यात प्रकार छान्नुहोस्' + nepali: 'निर्यात प्रकार छान्नुहोस्', + hindi: 'निर्यात प्रकार चुनें', + burmese: 'တင်ပို့မှု အမျိုးအစားကို ရွေးပါ', + thai: 'เลือกประเภทการส่งออก', + mandarin: '选择导出类型' }, shareLocally: { english: 'Share File', @@ -7176,7 +10759,11 @@ export const localizations = { brazilian_portuguese: 'Compartilhar arquivo', tok_pisin: 'Shareim file', indonesian: 'Bagikan file', - nepali: 'फाइल साझा गर्नुहोस्' + nepali: 'फाइल साझा गर्नुहोस्', + hindi: 'फाइल साझा करें', + burmese: 'ဖိုင်ကို မျှဝေပါ', + thai: 'แชร์ไฟล์', + mandarin: '分享文件' }, shareLocallyDescription: { english: 'Create a local audio file to save or share', @@ -7185,7 +10772,11 @@ export const localizations = { 'Criar um arquivo de áudio local para salvar ou compartilhar', tok_pisin: 'Mekim lokal audio fail long save o shareim', indonesian: 'Buat file audio lokal untuk disimpan atau dibagikan', - nepali: 'सुरक्षित वा साझा गर्न स्थानीय अडियो फाइल सिर्जना गर्नुहोस्' + nepali: 'सुरक्षित वा साझा गर्न स्थानीय अडियो फाइल सिर्जना गर्नुहोस्', + hindi: 'सहेजने या साझा करने के लिए एक स्थानीय ऑडियो फाइल बनाएं', + burmese: 'သိမ်းဆည်းရန် သို့မဟုတ် မျှဝေရန် ဒေသတွင်း အသံဖိုင်တစ်ခု ဖန်တီးပါ', + thai: 'สร้างไฟล์เสียงท้องถิ่นเพื่อบันทึกหรือแชร์', + mandarin: '创建本地音频文件以保存或共享' }, questExport: { english: 'Quest Export', @@ -7193,7 +10784,11 @@ export const localizations = { brazilian_portuguese: 'Exportação de Quest', tok_pisin: 'Quest Export', indonesian: 'Ekspor Quest', - nepali: 'क्वेस्ट निर्यात' + nepali: 'क्वेस्ट निर्यात', + hindi: 'क्वेस्ट निर्यात', + burmese: 'Quest တင်ပို့မှု', + thai: 'ส่งออกเควสต์', + mandarin: '任务导出' }, questExportDescription: { english: @@ -7207,7 +10802,13 @@ export const localizations = { indonesian: 'Ekspor pasal-pasal alkitab sebagai file audio untuk dibagikan dan didistribusikan', nepali: - 'साझेदारी र वितरणको लागि बाइबल अध्यायहरूलाई अडियो फाइलहरूको रूपमा निर्यात गर्नुहोस्' + 'साझेदारी र वितरणको लागि बाइबल अध्यायहरूलाई अडियो फाइलहरूको रूपमा निर्यात गर्नुहोस्', + hindi: + 'साझाकरण और वितरण के लिए बाइबल अध्यायों को ऑडियो फाइलों के रूप में निर्यात करें', + burmese: + 'မျှဝေရန်နှင့် ဖြန့်ဖြူးရန်အတွက် ကျမ်းစာ အခန်းများကို အသံဖိုင်များအဖြစ် တင်ပို့ပါ', + thai: 'ส่งออกบทพระคัมภีร์เป็นไฟล์เสียงเพื่อแชร์และแจกจ่าย', + mandarin: '将圣经章节导出为音频文件以供共享和分发' }, transcription: { english: 'Transcription', @@ -7215,7 +10816,11 @@ export const localizations = { brazilian_portuguese: 'Transcrição', tok_pisin: 'Transcription', indonesian: 'Transkripsi', - nepali: 'ट्रान्स्क्रिप्सन' + nepali: 'ट्रान्स्क्रिप्सन', + hindi: 'ट्रांसक्रिप्शन', + burmese: 'အသံဖြင့် ရေးသားခြင်း', + thai: 'การถอดความ', + mandarin: '转录' }, transcriptions: { english: 'Transcriptions', @@ -7223,7 +10828,11 @@ export const localizations = { brazilian_portuguese: 'Transcrições', tok_pisin: 'Ol Transcription', indonesian: 'Transkripsi', - nepali: 'ट्रान्स्क्रिप्सनहरू' + nepali: 'ट्रान्स्क्रिप्सनहरू', + hindi: 'ट्रांसक्रिप्शन', + burmese: 'အသံဖြင့် ရေးသားခြင်းများ', + thai: 'การถอดความ', + mandarin: '转录' }, noTranscriptionsYet: { english: 'No transcriptions yet. Be the first to transcribe!', @@ -7233,7 +10842,12 @@ export const localizations = { tok_pisin: 'I no gat transcription yet. Yu ken namba wan long transcribe!', indonesian: 'Belum ada transkripsi. Jadilah yang pertama mentranskripsi!', nepali: - 'अहिलेसम्म कुनै ट्रान्स्क्रिप्सन छैन। पहिलो ट्रान्स्क्राइबर बन्नुहोस्!' + 'अहिलेसम्म कुनै ट्रान्स्क्रिप्सन छैन। पहिलो ट्रान्स्क्राइबर बन्नुहोस्!', + hindi: + 'अभी तक कोई ट्रांसक्रिप्शन नहीं है। पहले ट्रांसक्राइब करने वाले बनें!', + burmese: 'အသံဖြင့် ရေးသားခြင်း မရှိသေးပါ။ ပထမဆုံး ရေးသားသူ ဖြစ်ပါ!', + thai: 'ยังไม่มีการถอดความ เป็นคนแรกที่ถอดความ!', + mandarin: '还没有转录。成为第一个转录的人!' }, transcriptionDescription: { english: 'Enable automatic transcription of audio recordings', @@ -7242,7 +10856,11 @@ export const localizations = { 'Habilitar transcrição automática de gravações de áudio', tok_pisin: 'Enablem automatic transcription bilong audio recordings', indonesian: 'Aktifkan transkripsi otomatis rekaman audio', - nepali: 'अडियो रेकर्डिङहरूको स्वचालित ट्रान्सक्रिप्सन सक्षम गर्नुहोस्' + nepali: 'अडियो रेकर्डिङहरूको स्वचालित ट्रान्सक्रिप्सन सक्षम गर्नुहोस्', + hindi: 'ऑडियो रिकॉर्डिंग की स्वचालित ट्रांसक्रिप्शन सक्षम करें', + burmese: 'အသံဖမ်းယူမှုများ၏ အလိုအလျောက် အသံဖြင့် ရေးသားခြင်းကို ဖွင့်ပါ', + thai: 'เปิดใช้งานการถอดความอัตโนมัติของการบันทึกเสียง', + mandarin: '启用音频录音的自动转录' }, transcriptionComplete: { english: 'Transcription Complete', @@ -7250,7 +10868,11 @@ export const localizations = { brazilian_portuguese: 'Transcrição concluída', tok_pisin: 'Transcription i pinis', indonesian: 'Transkripsi selesai', - nepali: 'ट्रान्स्क्रिप्सन पूरा भयो' + nepali: 'ट्रान्स्क्रिप्सन पूरा भयो', + hindi: 'ट्रांसक्रिप्शन पूर्ण', + burmese: 'အသံဖြင့် ရေးသားခြင်း ပြီးစီးပါပြီ', + thai: 'การถอดความเสร็จสมบูรณ์', + mandarin: '转录完成' }, copyFeedbackLink: { english: 'Copy Feedback Link', @@ -7258,7 +10880,11 @@ export const localizations = { brazilian_portuguese: 'Copiar link de feedback', tok_pisin: 'Kopim feedback link', indonesian: 'Salin Tautan Umpan Balik', - nepali: 'प्रतिक्रिया लिंक कपी गर्नुहोस्' + nepali: 'प्रतिक्रिया लिंक कपी गर्नुहोस्', + hindi: 'प्रतिक्रिया लिंक कॉपी करें', + burmese: 'အကြံပြုချက် လင့်ခ်ကို ကူးယူပါ', + thai: 'คัดลอกลิงก์ข้อเสนอแนะ', + mandarin: '复制反馈链接' }, copyFeedbackLinkDescription: { english: 'Copy a link to share for feedback', @@ -7266,7 +10892,11 @@ export const localizations = { brazilian_portuguese: 'Copiar um link para compartilhar para feedback', tok_pisin: 'Kopim link long shareim bilong feedback', indonesian: 'Salin tautan untuk dibagikan untuk umpan balik', - nepali: 'प्रतिक्रियाको लागि साझा गर्न लिंक कपी गर्नुहोस्' + nepali: 'प्रतिक्रियाको लागि साझा गर्न लिंक कपी गर्नुहोस्', + hindi: 'प्रतिक्रिया के लिए साझा करने के लिए एक लिंक कॉपी करें', + burmese: 'အကြံပြုချက်အတွက် မျှဝေရန် လင့်ခ်တစ်ခုကို ကူးယူပါ', + thai: 'คัดลอกลิงก์เพื่อแชร์สำหรับรับข้อเสนอแนะ', + mandarin: '复制链接以分享反馈' }, feedbackLinkNote: { english: @@ -7280,7 +10910,14 @@ export const localizations = { indonesian: 'Catatan: Kami berencana untuk mengimplementasikan tautan ke situs web LangQuest di mana ekspor dapat dilihat dan dikomentari di masa depan.', nepali: - 'नोट: हामी भविष्यमा LangQuest वेबसाइटमा लिंक लागू गर्ने योजना बनाइरहेका छौं जहाँ निर्यातहरू हेर्न र टिप्पणी गर्न सकिन्छ।' + 'नोट: हामी भविष्यमा LangQuest वेबसाइटमा लिंक लागू गर्ने योजना बनाइरहेका छौं जहाँ निर्यातहरू हेर्न र टिप्पणी गर्न सकिन्छ।', + hindi: + 'नोट: हम भविष्य में LangQuest वेबसाइट पर एक लिंक लागू करने की योजना बना रहे हैं जहां निर्यात देखे और टिप्पणी की जा सकेंगे।', + burmese: + 'မှတ်ချက်- အနာဂတ်တွင် ထုတ်ပို့မှုများကို ကြည့်ရှုနိုင်ပြီး မှတ်ချက်ပေးနိုင်သော LangQuest ဝက်ဘ်ဆိုဒ်သို့ လင့်ခ်တစ်ခု အကောင်အထည်ဖော်ရန် ကျွန်ုပ်တို့ စီစဉ်ထားပါသည်။', + thai: 'หมายเหตุ: เราวางแผนที่จะใช้ลิงก์ไปยังเว็บไซต์ LangQuest ซึ่งสามารถดูและแสดงความคิดเห็นเกี่ยวกับการส่งออกในอนาคต', + mandarin: + '注意:我们计划实施指向 LangQuest 网站的链接,将来可以在那里查看和评论导出内容。' }, linkCopied: { english: 'Link copied to clipboard!', @@ -7288,7 +10925,11 @@ export const localizations = { brazilian_portuguese: 'Link copiado para a área de transferência!', tok_pisin: 'Link kopim igo long clipboard!', indonesian: 'Tautan disalin ke clipboard!', - nepali: 'लिंक क्लिपबोर्डमा कपी भयो!' + nepali: 'लिंक क्लिपबोर्डमा कपी भयो!', + hindi: 'लिंक क्लिपबोर्ड पर कॉपी हो गया!', + burmese: 'လင့်ခ်ကို clipboard သို့ ကူးယူပြီးပါပြီ!', + thai: 'คัดลอกลิงก์ไปยังคลิปบอร์ดแล้ว!', + mandarin: '链接已复制到剪贴板!' }, verseMarkers: { english: 'Verse Labels', @@ -7296,7 +10937,11 @@ export const localizations = { brazilian_portuguese: 'Etiquetas de Versículos', tok_pisin: 'Verse Labels', indonesian: 'Label Versi', - nepali: 'पद लेबलहरू' + nepali: 'पद लेबलहरू', + hindi: 'पद लेबल', + burmese: 'ကျမ်းပိုဒ် စာညွှန်းများ', + thai: 'ป้ายกำกับข้อ', + mandarin: '经文标签' }, enableVerseLabelsQuestion: { english: 'Enable Verse Labels?', @@ -7304,7 +10949,11 @@ export const localizations = { brazilian_portuguese: 'Habilitar etiquetas de versículos?', tok_pisin: 'Enablem verse labels?', indonesian: 'Aktifkan label versi?', - nepali: 'पद लेबलहरू सक्षम गर्ने?' + nepali: 'पद लेबलहरू सक्षम गर्ने?', + hindi: 'पद लेबल सक्षम करें?', + burmese: 'ကျမ်းပိုဒ် စာညွှန်းများကို ဖွင့်မည်လား?', + thai: 'เปิดใช้งานป้ายกำกับข้อ?', + mandarin: '启用经文标签?' }, enableVerseLabelsDescription: { english: @@ -7318,7 +10967,14 @@ export const localizations = { indonesian: 'Fitur eksperimental ini membantu mengorganisir sumber daya Alkitab menggunakan label versi. Anda dapat mengaktifkan / menonaktifkannya kapan saja di menu Pengaturan.', nepali: - 'यो प्रयोगात्मक सुविधाले पद लेबलहरू प्रयोग गरेर बाइबल स्रोतहरू व्यवस्थित गर्न मद्दत गर्छ। तपाईं यसलाई सेटिङ्स मेनुमा जुनसुकै समय सक्षम / असक्षम गर्न सक्नुहुन्छ।' + 'यो प्रयोगात्मक सुविधाले पद लेबलहरू प्रयोग गरेर बाइबल स्रोतहरू व्यवस्थित गर्न मद्दत गर्छ। तपाईं यसलाई सेटिङ्स मेनुमा जुनसुकै समय सक्षम / असक्षम गर्न सक्नुहुन्छ।', + hindi: + 'यह प्रयोगात्मक सुविधा पद लेबल का उपयोग करके बाइबल संसाधनों को व्यवस्थित करने में मदद करती है। आप इसे सेटिंग्स मेनू में कभी भी सक्षम/असक्षम कर सकते हैं।', + burmese: + 'ဤစမ်းသပ်အင်္ဂါရပ်သည် ကျမ်းပိုဒ် စာညွှန်းများကို အသုံးပြု၍ ကျမ်းစာ အရင်းအမြစ်များကို စီစဉ်ရန် ကူညီပေးသည်။ သင်သည် Settings menu တွင် မည်သည့်အချိန်တွင်မဆို ဖွင့်/ပိတ်နိုင်သည်။', + thai: 'ฟีเจอร์ทดลองนี้ช่วยจัดระเบียบทรัพยากรพระคัมภีร์โดยใช้ป้ายกำกับข้อ คุณสามารถเปิด/ปิดได้ตลอดเวลาที่เมนูการตั้งค่า', + mandarin: + '此实验性功能使用经文标签帮助组织圣经资源。您可以随时在设置菜单中启用/禁用它。' }, verseMarkersDescription: { english: 'Enable verse labels to help organize Bible resources', @@ -7330,7 +10986,12 @@ export const localizations = { indonesian: 'Aktifkan label versi untuk membantu mengorganisir sumber daya Alkitab', nepali: - 'बाइबल स्रोतहरू व्यवस्थित गर्न मद्दत गर्न पद लेबलहरू सक्षम गर्नुहोस्' + 'बाइबल स्रोतहरू व्यवस्थित गर्न मद्दत गर्न पद लेबलहरू सक्षम गर्नुहोस्', + hindi: 'बाइबल संसाधनों को व्यवस्थित करने में मदद के लिए पद लेबल सक्षम करें', + burmese: + 'ကျမ်းစာ အရင်းအမြစ်များကို စီစဉ်ရန် ကူညီရန် ကျမ်းပိုဒ် စာညွှန်းများကို ဖွင့်ပါ', + thai: 'เปิดใช้งานป้ายกำกับข้อเพื่อช่วยจัดระเบียบทรัพยากรพระคัมภีร์', + mandarin: '启用经文标签以帮助组织圣经资源' }, // Languoid Link Suggestion strings languoidLinkSuggestionTitle: { @@ -7339,7 +11000,11 @@ export const localizations = { brazilian_portuguese: '¿Vincular seu idioma?', tok_pisin: 'Joinim tok ples bilong yu?', indonesian: 'Apakah Anda ingin menghubungkan bahasa Anda?', - nepali: 'आफ्नो भाषा लिंक गर्नुहुन्छ?' + nepali: 'आफ्नो भाषा लिंक गर्नुहुन्छ?', + hindi: 'अपनी भाषा लिंक करें?', + burmese: 'သင်၏ ဘာသာစကားကို ချိတ်ဆက်မည်လား?', + thai: 'เชื่อมโยงภาษาของคุณ?', + mandarin: '链接您的语言?' }, languoidLinkSuggestionDrawerTitle: { english: 'Link to existing language', @@ -7347,7 +11012,11 @@ export const localizations = { brazilian_portuguese: 'Vincular a um idioma existente', tok_pisin: 'Joinim wanpela tok ples', indonesian: 'Hubungkan ke bahasa yang ada', - nepali: 'अवस्थित भाषामा लिंक गर्नुहोस्' + nepali: 'अवस्थित भाषामा लिंक गर्नुहोस्', + hindi: 'मौजूदा भाषा से लिंक करें', + burmese: 'ရှိပြီးသား ဘာသာစကားသို့ ချိတ်ဆက်ပါ', + thai: 'เชื่อมโยงกับภาษาที่มีอยู่', + mandarin: '链接到现有语言' }, languoidLinkSuggestionDescription: { english: @@ -7361,7 +11030,14 @@ export const localizations = { indonesian: 'Kami menemukan bahasa yang ada yang mungkin cocok dengan yang Anda buat. Apakah Anda ingin menghubungkan ke bahasa yang ada?', nepali: - 'हामीले तपाईंले सिर्जना गर्नुभएको सँग मेल खान सक्ने अवस्थित भाषाहरू फेला पार्यौं। के तपाईं अवस्थित भाषामा लिंक गर्न चाहनुहुन्छ?' + 'हामीले तपाईंले सिर्जना गर्नुभएको सँग मेल खान सक्ने अवस्थित भाषाहरू फेला पार्यौं। के तपाईं अवस्थित भाषामा लिंक गर्न चाहनुहुन्छ?', + hindi: + 'हमें मौजूदा भाषाएं मिलीं जो आपके द्वारा बनाई गई भाषा से मेल खा सकती हैं। क्या आप मौजूदा भाषा से लिंक करना चाहेंगे?', + burmese: + 'သင်ဖန်တီးထားသော ဘာသာစကားနှင့် ကိုက်ညီနိုင်သော ရှိပြီးသား ဘာသာစကားများကို ကျွန်ုပ်တို့ တွေ့ရှိပါသည်။ ရှိပြီးသား ဘာသာစကားသို့ ချိတ်ဆက်လိုပါသလား?', + thai: 'เราพบภาษาที่มีอยู่ซึ่งอาจตรงกับภาษาที่คุณสร้าง คุณต้องการเชื่อมโยงกับภาษาที่มีอยู่หรือไม่?', + mandarin: + '我们找到了可能与您创建的语言匹配的现有语言。您想链接到现有语言吗?' }, yourLanguage: { english: 'Your language', @@ -7369,7 +11045,11 @@ export const localizations = { brazilian_portuguese: 'Seu idioma', tok_pisin: 'Tok ples bilong yu', indonesian: 'Bahasa Anda', - nepali: 'तपाईंको भाषा' + nepali: 'तपाईंको भाषा', + hindi: 'आपकी भाषा', + burmese: 'သင်၏ ဘာသာစကား', + thai: 'ภาษาของคุณ', + mandarin: '您的语言' }, seeLanguageSuggestions: { english: 'See language suggestions', @@ -7377,7 +11057,11 @@ export const localizations = { brazilian_portuguese: 'Ver sugestões de idioma', tok_pisin: 'Lukim ol tok ples bilong en', indonesian: 'Lihat sugesti bahasa', - nepali: 'भाषा सुझावहरू हेर्नुहोस्' + nepali: 'भाषा सुझावहरू हेर्नुहोस्', + hindi: 'भाषा सुझाव देखें', + burmese: 'ဘာသာစကား အကြံပြုချက်များကို ကြည့်ပါ', + thai: 'ดูคำแนะนำภาษา', + mandarin: '查看语言建议' }, keepMyLanguage: { english: 'Keep my language', @@ -7385,7 +11069,11 @@ export const localizations = { brazilian_portuguese: 'Manter meu idioma', tok_pisin: 'Holim tok ples bilong mi', indonesian: 'Simpan bahasa saya', - nepali: 'मेरो भाषा राख्नुहोस्' + nepali: 'मेरो भाषा राख्नुहोस्', + hindi: 'मेरी भाषा रखें', + burmese: 'ကျွန်ုပ်၏ ဘာသာစကားကို ထားပါ', + thai: 'เก็บภาษาของฉัน', + mandarin: '保留我的语言' }, chooseThisLanguage: { english: 'Choose this language', @@ -7393,7 +11081,11 @@ export const localizations = { brazilian_portuguese: 'Escolher este idioma', tok_pisin: 'Pilim dispela tok ples', indonesian: 'Pilih bahasa ini', - nepali: 'यो भाषा छान्नुहोस्' + nepali: 'यो भाषा छान्नुहोस्', + hindi: 'यह भाषा चुनें', + burmese: 'ဤဘာသာစကားကို ရွေးပါ', + thai: 'เลือกภาษานี้', + mandarin: '选择此语言' }, exactMatch: { english: 'Exact match', @@ -7401,7 +11093,11 @@ export const localizations = { brazilian_portuguese: 'Correspondência exata', tok_pisin: 'Sem tru', indonesian: 'Kecocokan persis', - nepali: 'ठीक मिल्यो' + nepali: 'ठीक मिल्यो', + hindi: 'सटीक मिलान', + burmese: 'တိကျသော ကိုက်ညီမှု', + thai: 'ตรงกันทุกประการ', + mandarin: '完全匹配' }, partialMatch: { english: 'Partial match', @@ -7409,7 +11105,11 @@ export const localizations = { brazilian_portuguese: 'Correspondência parcial', tok_pisin: 'Luk olsem', indonesian: 'Kecocokan sebagian', - nepali: 'आंशिक मिल्यो' + nepali: 'आंशिक मिल्यो', + hindi: 'आंशिक मिलान', + burmese: 'တစ်စိတ်တစ်ပိုင်း ကိုက်ညီမှု', + thai: 'ตรงกันบางส่วน', + mandarin: '部分匹配' }, matchedByName: { english: 'Matched by name', @@ -7417,7 +11117,11 @@ export const localizations = { brazilian_portuguese: 'Correspondido por nome', tok_pisin: 'Painim long nem', indonesian: 'Cocok berdasarkan nama', - nepali: 'नामद्वारा मेल खायो' + nepali: 'नामद्वारा मेल खायो', + hindi: 'नाम से मेल खाया', + burmese: 'အမည်ဖြင့် ကိုက်ညီသည်', + thai: 'ตรงกันตามชื่อ', + mandarin: '按名称匹配' }, matchedByAlias: { english: 'Matched by alias', @@ -7425,7 +11129,11 @@ export const localizations = { brazilian_portuguese: 'Correspondido por alias', tok_pisin: 'Painim long narapela nem', indonesian: 'Cocok berdasarkan alias', - nepali: 'उपनामद्वारा मेल खायो' + nepali: 'उपनामद्वारा मेल खायो', + hindi: 'उपनाम से मेल खाया', + burmese: 'အမည်ပြောင်ဖြင့် ကိုက်ညီသည်', + thai: 'ตรงกันตามนามแฝง', + mandarin: '按别名匹配' }, matchedByIsoCode: { english: 'Matched by ISO code', @@ -7433,7 +11141,11 @@ export const localizations = { brazilian_portuguese: 'Correspondido por código ISO', tok_pisin: 'Painim long ISO kod', indonesian: 'Cocok berdasarkan kode ISO', - nepali: 'ISO कोडद्वारा मेल खायो' + nepali: 'ISO कोडद्वारा मेल खायो', + hindi: 'ISO कोड से मेल खाया', + burmese: 'ISO ကုဒ်ဖြင့် ကိုက်ညီသည်', + thai: 'ตรงกันตามรหัส ISO', + mandarin: '按ISO代码匹配' }, languageLinkSuccess: { english: 'Language linked successfully', @@ -7441,7 +11153,11 @@ export const localizations = { brazilian_portuguese: 'Idioma vinculado com sucesso', tok_pisin: 'Tok ples joinim gut', indonesian: 'Bahasa berhasil dihubungkan', - nepali: 'भाषा सफलतापूर्वक लिंक भयो' + nepali: 'भाषा सफलतापूर्वक लिंक भयो', + hindi: 'भाषा सफलतापूर्वक लिंक हो गई', + burmese: 'ဘာသာစကားကို အောင်မြင်စွာ ချိတ်ဆက်ပြီးပါပြီ', + thai: 'เชื่อมโยงภาษาสำเร็จแล้ว', + mandarin: '语言链接成功' }, languageLinkError: { english: 'Failed to link language', @@ -7449,7 +11165,11 @@ export const localizations = { brazilian_portuguese: 'Falha ao vincular idioma', tok_pisin: 'No inap joinim tok ples', indonesian: 'Gagal menghubungkan bahasa', - nepali: 'भाषा लिंक गर्न असफल' + nepali: 'भाषा लिंक गर्न असफल', + hindi: 'भाषा लिंक करने में विफल', + burmese: 'ဘာသာစကားကို ချိတ်ဆက်ရန် မအောင်မြင်ပါ', + thai: 'เชื่อมโยงภาษาไม่สำเร็จ', + mandarin: '链接语言失败' }, keepLanguageSuccess: { english: 'Your custom language has been kept', @@ -7457,7 +11177,11 @@ export const localizations = { brazilian_portuguese: 'Seu idioma personalizado foi mantido', tok_pisin: 'Tok ples bilong yu i stap yet', indonesian: 'Bahasa kustom Anda telah disimpan', - nepali: 'तपाईंको आफ्नै भाषा राखिएको छ' + nepali: 'तपाईंको आफ्नै भाषा राखिएको छ', + hindi: 'आपकी कस्टम भाषा रखी गई है', + burmese: 'သင်၏ စိတ်ကြိုက် ဘာသာစကားကို ထားရှိပြီးပါပြီ', + thai: 'เก็บภาษาที่กำหนดเองของคุณแล้ว', + mandarin: '您的自定义语言已保留' }, enableLanguoidLinkSuggestions: { english: 'Language link suggestions', @@ -7465,7 +11189,11 @@ export const localizations = { brazilian_portuguese: 'Sugestões de vinculação de idioma', tok_pisin: 'Ol tok ples bilong joinim', indonesian: 'Saran tautan bahasa', - nepali: 'भाषा लिंक सुझावहरू' + nepali: 'भाषा लिंक सुझावहरू', + hindi: 'भाषा लिंक सुझाव', + burmese: 'ဘာသာစကား ချိတ်ဆက်မှု အကြံပြုချက်များ', + thai: 'คำแนะนำการเชื่อมโยงภาษา', + mandarin: '语言链接建议' }, enableLanguoidLinkSuggestionsDescription: { english: @@ -7479,7 +11207,13 @@ export const localizations = { indonesian: 'Dapatkan saran untuk menghubungkan bahasa kustom Anda dengan yang ada di database', nepali: - 'आफ्नो आफ्नै-सिर्जना गरिएका भाषाहरूलाई डाटाबेसमा अवस्थित भाषाहरूसँग लिंक गर्न सुझावहरू प्राप्त गर्नुहोस्' + 'आफ्नो आफ्नै-सिर्जना गरिएका भाषाहरूलाई डाटाबेसमा अवस्थित भाषाहरूसँग लिंक गर्न सुझावहरू प्राप्त गर्नुहोस्', + hindi: + 'अपनी कस्टम-बनाई गई भाषाओं को डेटाबेस में मौजूदा भाषाओं से लिंक करने के लिए सुझाव प्राप्त करें', + burmese: + 'သင်၏ စိတ်ကြိုက် ဖန်တီးထားသော ဘာသာစကားများကို ဒေတာဘေ့စ်ရှိ ရှိပြီးသား ဘာသာစကားများသို့ ချိတ်ဆက်ရန် အကြံပြုချက်များ ရယူပါ', + thai: 'รับคำแนะนำเพื่อเชื่อมโยงภาษาที่คุณสร้างเองกับภาษาที่มีอยู่ในฐานข้อมูล', + mandarin: '获取建议,将您自定义创建的语言链接到数据库中的现有语言' }, enableMerge: { english: 'Merge assets', @@ -7487,7 +11221,11 @@ export const localizations = { brazilian_portuguese: 'Mesclar ativos', tok_pisin: 'Joinim ol aset', indonesian: 'Gabungkan aset', - nepali: 'सम्पत्तिहरू मर्ज गर्नुहोस्' + nepali: 'सम्पत्तिहरू मर्ज गर्नुहोस्', + hindi: 'एसेट मर्ज करें', + burmese: 'ပိုင်ဆိုင်မှုများကို ပေါင်းစပ်ပါ', + thai: 'รวมสินทรัพย์', + mandarin: '合并资产' }, enableMergeDescription: { english: @@ -7501,7 +11239,14 @@ export const localizations = { indonesian: 'Izinkan penggabungan beberapa aset audio menjadi satu aset. Gunakan dengan hati-hati — urutan segmen mungkin perlu penyesuaian manual setelah penggabungan.', nepali: - 'धेरै अडियो सम्पत्तिहरूलाई एकल सम्पत्तिमा मर्ज गर्न अनुमति दिनुहोस्। सावधानीपूर्वक प्रयोग गर्नुहोस् — मर्ज गरेपछि खण्ड क्रम म्यानुअल समायोजन आवश्यक पर्न सक्छ।' + 'धेरै अडियो सम्पत्तिहरूलाई एकल सम्पत्तिमा मर्ज गर्न अनुमति दिनुहोस्। सावधानीपूर्वक प्रयोग गर्नुहोस् — मर्ज गरेपछि खण्ड क्रम म्यानुअल समायोजन आवश्यक पर्न सक्छ।', + hindi: + 'कई ऑडियो एसेट को एक एसेट में मर्ज करने की अनुमति दें। सावधानी से उपयोग करें — मर्ज करने के बाद सेगमेंट क्रम को मैन्युअल रूप से समायोजित करने की आवश्यकता हो सकती है।', + burmese: + 'အသံပိုင်ဆိုင်မှုများစွာကို တစ်ခုတည်းသော ပိုင်ဆိုင်မှုသို့ ပေါင်းစပ်ခွင့်ပြုပါ။ သတိဖြင့် အသုံးပြုပါ — ပေါင်းစပ်ပြီးနောက် အပိုင်းအစ အစဉ်ကို လက်ဖြင့် ညှိယူရန် လိုအပ်နိုင်သည်။', + thai: 'อนุญาตให้รวมสินทรัพย์เสียงหลายรายการเป็นสินทรัพย์เดียว ใช้ด้วยความระมัดระวัง — ลำดับของส่วนอาจต้องปรับด้วยตนเองหลังจากรวม', + mandarin: + '允许将多个音频资产合并为单个资产。请谨慎使用 — 合并后可能需要手动调整片段顺序。' } } as const; diff --git a/services/migrationBackupService.ts b/services/migrationBackupService.ts index 87a6c075e..8a39adce3 100644 --- a/services/migrationBackupService.ts +++ b/services/migrationBackupService.ts @@ -79,7 +79,7 @@ function getBackupDir(): string { */ function getDbPath(): string { const docDir = getDocumentDirectory() ?? ''; - return `${docDir}${DB_FILENAME}`; + return `${docDir}/${DB_FILENAME}`; } /** @@ -422,7 +422,7 @@ export async function restoreFromBackup( * Read directory contents (platform-specific) * Returns empty array on web since OPFS directory listing would require additional setup */ -async function readBackupDirectory(): Promise { +function readBackupDirectory(): string[] { if (Platform.OS === 'web') { // Web OPFS directory listing not implemented - return empty // This means cleanup/recovery functions won't work on web, @@ -433,16 +433,18 @@ async function readBackupDirectory(): Promise { return []; } - // Dynamic import for native platforms - const FileSystem = await import('expo-file-system'); + // Use the new expo-file-system API + const { Directory } = + require('expo-file-system') as typeof import('expo-file-system'); const backupDir = getBackupDir(); - const dirInfo = await FileSystem.getInfoAsync(backupDir); + const dir = new Directory(backupDir); - if (!dirInfo.exists) { + if (!dir.exists) { return []; } - return FileSystem.readDirectoryAsync(backupDir); + // dir.list() returns File and Directory instances, extract names + return dir.list().map((item) => item.name); } /** @@ -460,7 +462,7 @@ export async function cleanupOldBackups( console.log('[MigrationBackup] Cleaning up old backups...'); try { - const files = await readBackupDirectory(); + const files = readBackupDirectory(); if (files.length === 0) { console.log('[MigrationBackup] No backup files to clean'); @@ -496,7 +498,7 @@ export async function cleanupOldBackups( file.startsWith(LOCAL_STORE_BACKUP_PREFIX) ) { try { - await deleteIfExists(`${backupDir}${file}`); + deleteIfExists(`${backupDir}${file}`); deletedCount++; } catch (error) { console.warn( @@ -507,7 +509,7 @@ export async function cleanupOldBackups( } } - console.log(`[MigrationBackup] ✓ Cleaned up ${deletedCount} old backup(s)`); + console.log(`[MigrationBackup] Cleaned up ${deletedCount} old backup(s)`); } catch (error) { console.warn('[MigrationBackup] Error cleaning up backups:', error); // Don't throw - cleanup failure shouldn't break the app @@ -547,7 +549,7 @@ export async function deleteBackup(backupInfo: BackupInfo): Promise { */ export async function hasValidBackup(): Promise { try { - const files = await readBackupDirectory(); + const files = readBackupDirectory(); if (files.length === 0) { return false; @@ -573,7 +575,7 @@ export async function hasValidBackup(): Promise { */ export async function getMostRecentBackup(): Promise { try { - const files = await readBackupDirectory(); + const files = readBackupDirectory(); if (files.length === 0) { return null; diff --git a/store/localStore.ts b/store/localStore.ts index 988023ad6..d7440cb4e 100644 --- a/store/localStore.ts +++ b/store/localStore.ts @@ -403,7 +403,11 @@ export const useLocalStore = create()( setAnalyticsOptOut: (optOut) => set({ analyticsOptOut: optOut }), setTheme: (theme) => { set({ theme }); - colorScheme.set(theme); + // Only set colorScheme if NativeWind is initialized and theme is not 'system' + // 'system' theme should use the OS color scheme, not be set explicitly + if (colorScheme && theme !== 'system') { + colorScheme.set(theme); + } }, setUILanguage: (lang) => set({ uiLanguage: lang }), setSavedLanguage: (lang) => set({ savedLanguage: lang }), @@ -587,7 +591,7 @@ export const useLocalStore = create()( onRehydrateStorage: () => async (state) => { console.log('rehydrating local store', state); if (state) { - colorScheme.set(state.theme); + state.setTheme(state.theme); // Validate and clamp VAD threshold if invalid if ( typeof state.vadThreshold !== 'number' || @@ -633,8 +637,8 @@ export const useLocalStore = create()( 'currentUser', // I don't think we're getting this from the local store any more 'currentProjectId', 'currentQuestId', - 'currentAssetId', - 'navigationStack' + 'currentAssetId' + //'navigationStack' //commented out so the app can remember the last view on reload ].includes(key) ) ) diff --git a/supabase/functions/send-email/_templates/confirm-email.tsx b/supabase/functions/send-email/_templates/confirm-email.tsx index 54a20a7e3..093ec0706 100644 --- a/supabase/functions/send-email/_templates/confirm-email.tsx +++ b/supabase/functions/send-email/_templates/confirm-email.tsx @@ -85,6 +85,41 @@ export const ConfirmEmail = ({ button: 'खाता पुष्टि गर्नुहोस्', orCopy: 'वा यो लिंक तपाईंको ब्राउजरमा कपि र पेस्ट गर्नुहोस्:', expiry: 'यो लिंक २४ घण्टामा समाप्त हुनेछ।' + }, + hi: { + preview: 'अपना LangQuest खाता पुष्टि करें', + title: 'अपना खाता पुष्टि करें', + description: + 'अपना खाता पुष्टि करने और पंजीकरण पूरा करने के लिए इस लिंक का अनुसरण करें:', + button: 'खाता पुष्टि करें', + orCopy: 'या अपने ब्राउज़र में इस लिंक को कॉपी और पेस्ट करें:', + expiry: 'यह लिंक 24 घंटे में समाप्त हो जाएगा।' + }, + my: { + preview: 'သင်၏ LangQuest အကောင့်ကို အတည်ပြုပါ', + title: 'သင်၏ အကောင့်ကို အတည်ပြုပါ', + description: + 'သင်၏ အကောင့်ကို အတည်ပြုရန်နှင့် မှတ်ပုံတင်ခြင်းကို ပြီးမြောက်စေရန် ဤလင့်ခ်ကို လိုက်နာပါ:', + button: 'အကောင့် အတည်ပြုပါ', + orCopy: 'သို့မဟုတ် ဤလင့်ခ်ကို သင်၏ ဘရောက်ဆာတွင် ကူးယူ၍ ထည့်ပါ:', + expiry: 'ဤလင့်ခ်သည် 24 နာရီအတွင်း သက်တမ်းကုန်ဆုံးမည်။' + }, + th: { + preview: 'ยืนยันบัญชี LangQuest ของคุณ', + title: 'ยืนยันบัญชีของคุณ', + description: + 'ทำตามลิงก์นี้เพื่อยืนยันบัญชีของคุณและทำการลงทะเบียนให้เสร็จสมบูรณ์:', + button: 'ยืนยันบัญชี', + orCopy: 'หรือคัดลอกและวางลิงก์นี้ในเบราว์เซอร์ของคุณ:', + expiry: 'ลิงก์นี้จะหมดอายุใน 24 ชั่วโมง' + }, + 'zh-CN': { + preview: '确认您的 LangQuest 账户', + title: '确认您的账户', + description: '请点击此链接以确认您的账户并完成注册:', + button: '确认账户', + orCopy: '或者将此链接复制并粘贴到您的浏览器中:', + expiry: '此链接将在 24 小时后过期。' } }; diff --git a/supabase/functions/send-email/_templates/invite-email.tsx b/supabase/functions/send-email/_templates/invite-email.tsx index f98b37e3b..93f2dccc5 100644 --- a/supabase/functions/send-email/_templates/invite-email.tsx +++ b/supabase/functions/send-email/_templates/invite-email.tsx @@ -116,6 +116,56 @@ export const InviteEmail = ({ button: 'LangQuest मा सामेल हुनुहोस्', orCopy: 'वा यो लिंक तपाईंको ब्राउजरमा कपि र पेस्ट गर्नुहोस्:', expiry: 'यो आमन्त्रण लिंक ७ दिनमा समाप्त हुनेछ।' + }, + hi: { + preview: `आपको ${projectName} में LangQuest में शामिल होने के लिए आमंत्रित किया गया है`, + title: 'प्रोजेक्ट आमंत्रण', + greeting: 'नमस्ते!', + description: `${inviterName} ने आपको LangQuest पर "${projectName}" प्रोजेक्ट में शामिल होने के लिए आमंत्रित किया है, एक सहयोगी भाषा सीखने का मंच।`, + whatIsLangQuest: + 'LangQuest समुदायों को भाषा सीखने के संसाधन बनाने और साझा करने में मदद करता है। अनुवाद, ऑडियो रिकॉर्डिंग में योगदान देने और दुनिया भर की भाषाओं को संरक्षित करने में मदद करने के लिए हमसे जुड़ें।', + instruction: + 'अपना खाता बनाने और प्रोजेक्ट में शामिल होने के लिए नीचे दिए गए बटन पर क्लिक करें:', + button: 'LangQuest में शामिल हों', + orCopy: 'या अपने ब्राउज़र में इस लिंक को कॉपी और पेस्ट करें:', + expiry: 'यह आमंत्रण लिंक 7 दिनों में समाप्त हो जाएगा।' + }, + my: { + preview: `သင့်အား ${projectName} တွင် LangQuest တွင် ပါဝင်ရန် ဖိတ်ခေါ်ထားပါသည်`, + title: 'စီမံကိန်း ဖိတ်ခေါ်ခြင်း', + greeting: 'မင်္ဂလာပါ!', + description: `${inviterName} သည် သင့်အား LangQuest တွင် "${projectName}" စီမံကိန်းတွင် ပါဝင်ရန် ဖိတ်ခေါ်ထားပါသည်၊ ပူးပေါင်းဆောင်ရွက်သော ဘာသာစကား သင်ယူမှု ပလက်ဖောင်းတစ်ခုဖြစ်သည်။`, + whatIsLangQuest: + 'LangQuest သည် အသိုင်းအဝိုင်းများကို ဘာသာစကား သင်ယူမှု အရင်းအမြစ်များ ဖန်တီးရန်နှင့် မျှဝေရန် ကူညီပေးသည်။ ဘာသာပြန်ဆိုခြင်း၊ အသံဖမ်းယူခြင်းများတွင် ပါဝင်ဆောင်ရွက်ရန်နှင့် ကမ္ဘာတစ်ဝှမ်းရှိ ဘာသာစကားများကို ထိန်းသိမ်းရန် ကူညီရန် ကျွန်ုပ်တို့နှင့် ပူးပေါင်းပါ။', + instruction: + 'သင်၏ အကောင့်ကို ဖန်တီးရန်နှင့် စီမံကိန်းတွင် ပါဝင်ရန် အောက်ပါ ခလုတ်ကို နှိပ်ပါ:', + button: 'LangQuest တွင် ပါဝင်ပါ', + orCopy: 'သို့မဟုတ် ဤလင့်ခ်ကို သင်၏ ဘရောက်ဆာတွင် ကူးယူ၍ ထည့်ပါ:', + expiry: 'ဤဖိတ်ခေါ်ခြင်း လင့်ခ်သည် 7 ရက်အတွင်း သက်တမ်းကုန်ဆုံးမည်။' + }, + th: { + preview: `คุณได้รับเชิญให้เข้าร่วม ${projectName} ใน LangQuest`, + title: 'คำเชิญเข้าร่วมโครงการ', + greeting: 'สวัสดี!', + description: `${inviterName} ได้เชิญคุณให้เข้าร่วมโครงการ "${projectName}" ใน LangQuest ซึ่งเป็นแพลตฟอร์มการเรียนรู้ภาษาที่ทำงานร่วมกัน`, + whatIsLangQuest: + 'LangQuest ช่วยให้ชุมชนสร้างและแบ่งปันทรัพยากรการเรียนรู้ภาษา เข้าร่วมกับเราเพื่อมีส่วนร่วมในการแปล บันทึกเสียง และช่วยรักษาภาษาทั่วโลก', + instruction: 'คลิกปุ่มด้านล่างเพื่อสร้างบัญชีของคุณและเข้าร่วมโครงการ:', + button: 'เข้าร่วม LangQuest', + orCopy: 'หรือคัดลอกและวางลิงก์นี้ในเบราว์เซอร์ของคุณ:', + expiry: 'ลิงก์คำเชิญนี้จะหมดอายุใน 7 วัน' + }, + 'zh-CN': { + preview: `您已被邀请加入 LangQuest 上的 ${projectName}`, + title: '项目邀请', + greeting: '您好!', + description: `${inviterName} 邀请您加入 LangQuest 上的项目 "${projectName}",这是一个协作式语言学习平台。`, + whatIsLangQuest: + 'LangQuest 帮助社区创建和分享语言学习资源。加入我们,贡献翻译、音频录制,并帮助保护世界各地的语言。', + instruction: '点击下面的按钮创建您的账户并加入项目:', + button: '加入 LangQuest', + orCopy: '或者将此链接复制并粘贴到您的浏览器中:', + expiry: '此邀请链接将在 7 天后过期。' } }; diff --git a/supabase/functions/send-email/_templates/reset-password.tsx b/supabase/functions/send-email/_templates/reset-password.tsx index 793160be5..b8c350118 100644 --- a/supabase/functions/send-email/_templates/reset-password.tsx +++ b/supabase/functions/send-email/_templates/reset-password.tsx @@ -101,6 +101,52 @@ export const ResetPassword = ({ button: 'पासवर्ड रिसेट गर्नुहोस्', orCopy: 'वा यो लिंक तपाईंको ब्राउजरमा कपि र पेस्ट गर्नुहोस्:', expiry: 'यो लिंक २४ घण्टामा समाप्त हुनेछ।' + }, + hi: { + preview: 'अपना LangQuest पासवर्ड रीसेट करें', + title: 'अपना पासवर्ड रीसेट करें', + greeting: 'नमस्ते,', + description: + 'किसी ने आपके LangQuest खाते के लिए पासवर्ड रीसेट का अनुरोध किया है। यदि यह आप नहीं थे, तो कृपया इस ईमेल को अनदेखा करें।', + instruction: + 'अपना पासवर्ड रीसेट करने के लिए नीचे दिए गए बटन पर क्लिक करें:', + button: 'पासवर्ड रीसेट करें', + orCopy: 'या अपने ब्राउज़र में इस लिंक को कॉपी और पेस्ट करें:', + expiry: 'यह लिंक 24 घंटे में समाप्त हो जाएगा।' + }, + my: { + preview: 'သင်၏ LangQuest စကားဝှက်ကို ပြန်လည်သတ်မှတ်ပါ', + title: 'သင်၏ စကားဝှက်ကို ပြန်လည်သတ်မှတ်ပါ', + greeting: 'မင်္ဂလာပါ၊', + description: + 'တစ်စုံတစ်ယောက်က သင်၏ LangQuest အကောင့်အတွက် စကားဝှက် ပြန်လည်သတ်မှတ်ရန် တောင်းဆိုထားပါသည်။ သင်မဟုတ်ပါက ဤအီးမေးလ်ကို လျစ်လျူရှုပါ။', + instruction: + 'သင်၏ စကားဝှက်ကို ပြန်လည်သတ်မှတ်ရန် အောက်ပါ ခလုတ်ကို နှိပ်ပါ:', + button: 'စကားဝှက် ပြန်လည်သတ်မှတ်ပါ', + orCopy: 'သို့မဟုတ် ဤလင့်ခ်ကို သင်၏ ဘရောက်ဆာတွင် ကူးယူ၍ ထည့်ပါ:', + expiry: 'ဤလင့်ခ်သည် 24 နာရီအတွင်း သက်တမ်းကုန်ဆုံးမည်။' + }, + th: { + preview: 'รีเซ็ตรหัสผ่าน LangQuest ของคุณ', + title: 'รีเซ็ตรหัสผ่านของคุณ', + greeting: 'สวัสดีครับ/ค่ะ', + description: + 'มีคนขอรีเซ็ตรหัสผ่านสำหรับบัญชี LangQuest ของคุณ หากไม่ใช่คุณ กรุณาเพิกเฉยต่ออีเมลนี้', + instruction: 'คลิกปุ่มด้านล่างเพื่อรีเซ็ตรหัสผ่านของคุณ:', + button: 'รีเซ็ตรหัสผ่าน', + orCopy: 'หรือคัดลอกและวางลิงก์นี้ในเบราว์เซอร์ของคุณ:', + expiry: 'ลิงก์นี้จะหมดอายุใน 24 ชั่วโมง' + }, + 'zh-CN': { + preview: '重置您的 LangQuest 密码', + title: '重置您的密码', + greeting: '您好,', + description: + '有人请求重置您的 LangQuest 账户密码。如果不是您,请忽略此邮件。', + instruction: '点击下面的按钮重置您的密码:', + button: '重置密码', + orCopy: '或者将此链接复制并粘贴到您的浏览器中:', + expiry: '此链接将在 24 小时后过期。' } }; diff --git a/supabase/functions/send-email/index.ts b/supabase/functions/send-email/index.ts index 21bfdd510..baa44eae1 100644 --- a/supabase/functions/send-email/index.ts +++ b/supabase/functions/send-email/index.ts @@ -23,7 +23,11 @@ const signupEmailSubjects = { 'pt-BR': 'Confirme sua conta LangQuest', 'id-ID': 'Konfirmasi Akun LangQuest Anda', 'tpi-PG': 'Strongim LangQuest Akaun bilong yu', - ne: 'तपाईंको LangQuest खाता पुष्टि गर्नुहोस्' + ne: 'तपाईंको LangQuest खाता पुष्टि गर्नुहोस्', + hi: 'अपना LangQuest खाता पुष्टि करें', + my: 'သင်၏ LangQuest အကောင့်ကို အတည်ပြုပါ', + th: 'ยืนยันบัญชี LangQuest ของคุณ', + 'zh-CN': '确认您的 LangQuest 账户' }; // Email subject translations const emailSubjects = { @@ -36,7 +40,11 @@ const emailSubjects = { 'pt-BR': 'Redefina sua senha do LangQuest', 'id-ID': 'Atur Ulang Kata Sandi LangQuest Anda', 'tpi-PG': 'Resetim LangQuest Password bilong yu', - ne: 'तपाईंको LangQuest पासवर्ड रिसेट गर्नुहोस्' + ne: 'तपाईंको LangQuest पासवर्ड रिसेट गर्नुहोस्', + hi: 'अपना LangQuest पासवर्ड रीसेट करें', + my: 'သင်၏ LangQuest စကားဝှက်ကို ပြန်လည်သတ်မှတ်ပါ', + th: 'รีเซ็ตรหัสผ่าน LangQuest ของคุณ', + 'zh-CN': '重置您的 LangQuest 密码' }, invite: { en: "You've been invited to join a project on LangQuest", @@ -45,7 +53,11 @@ const emailSubjects = { 'pt-BR': 'Você foi convidado para participar de um projeto no LangQuest', 'id-ID': 'Anda telah diundang untuk bergabung dalam proyek di LangQuest', 'tpi-PG': 'Yu telah strongim langquest bilong yu', - ne: 'तपाईंलाई LangQuest मा एउटा प्रोजेक्टमा सामेल हुन आमन्त्रित गरिएको छ' + ne: 'तपाईंलाई LangQuest मा एउटा प्रोजेक्टमा सामेल हुन आमन्त्रित गरिएको छ', + hi: 'आपको LangQuest पर एक प्रोजेक्ट में शामिल होने के लिए आमंत्रित किया गया है', + my: 'သင့်အား LangQuest တွင် စီမံကိန်းတစ်ခုတွင် ပါဝင်ရန် ဖိတ်ခေါ်ထားပါသည်', + th: 'คุณได้รับเชิญให้เข้าร่วมโครงการใน LangQuest', + 'zh-CN': '您已被邀请加入 LangQuest 上的项目' } }; const emailTypeEndpoint = { @@ -72,7 +84,14 @@ function mapLanguoidNameToLocale( 'tok pisin': 'tpi-PG', 'standard indonesian': 'id-ID', indonesian: 'id-ID', // Also handle just "Indonesian" - nepali: 'ne' + nepali: 'ne', + hindi: 'hi', + burmese: 'my', + myanmar: 'my', + thai: 'th', + mandarin: 'zh-CN', + 'mandarin chinese': 'zh-CN', + chinese: 'zh-CN' }; return mapping[normalized] ?? 'en'; diff --git a/supabase/migrations/20260217000000_add_hindi_burmese_thai_mandarin_ui_ready.sql b/supabase/migrations/20260217000000_add_hindi_burmese_thai_mandarin_ui_ready.sql new file mode 100644 index 000000000..0d95f8455 --- /dev/null +++ b/supabase/migrations/20260217000000_add_hindi_burmese_thai_mandarin_ui_ready.sql @@ -0,0 +1,167 @@ +-- Add Hindi, Burmese, Thai, and Mandarin languoids to dev branch +-- These languoids already exist in production with specific IDs +-- Production IDs: +-- Hindi: 3502e2a1-3cfd-45f7-8167-a9a67d42c76a +-- Burmese: fa8369a5-03a6-4d1e-ba44-152409a2f97c +-- Thai: fe361436-f7ff-4955-a442-59b76623b3c9 +-- Mandarin: 0d75d06f-2692-4127-b810-67dd64fa6eee +-- +-- In local dev, the languoids are created by seeds (which run AFTER migrations), +-- so we use conditional statements that only execute if the languoids exist. + +-- ============================================================================ +-- Hindi (हिन्दी) +-- ISO 639-3: hin +-- ============================================================================ + +-- Add Hindi endonymic aliases (हिन्दी, हिंदी) - only if the languoid exists +INSERT INTO public.languoid_alias ( + subject_languoid_id, + label_languoid_id, + name, + alias_type, + source_names +) +SELECT + '3502e2a1-3cfd-45f7-8167-a9a67d42c76a', + '3502e2a1-3cfd-45f7-8167-a9a67d42c76a', + 'हिन्दी', + 'endonym'::public.alias_type, + ARRAY['lexvo'] +WHERE EXISTS ( + SELECT 1 FROM public.languoid WHERE id = '3502e2a1-3cfd-45f7-8167-a9a67d42c76a' +) +ON CONFLICT (subject_languoid_id, label_languoid_id, alias_type, name) DO NOTHING; + +INSERT INTO public.languoid_alias ( + subject_languoid_id, + label_languoid_id, + name, + alias_type, + source_names +) +SELECT + '3502e2a1-3cfd-45f7-8167-a9a67d42c76a', + '3502e2a1-3cfd-45f7-8167-a9a67d42c76a', + 'हिंदी', + 'endonym'::public.alias_type, + ARRAY['lexvo'] +WHERE EXISTS ( + SELECT 1 FROM public.languoid WHERE id = '3502e2a1-3cfd-45f7-8167-a9a67d42c76a' +) +ON CONFLICT (subject_languoid_id, label_languoid_id, alias_type, name) DO NOTHING; + +-- ============================================================================ +-- Burmese (မြန်မာ) +-- ISO 639-3: mya +-- ============================================================================ + +-- Add Burmese endonymic alias (မြန်မာ) - only if the languoid exists +INSERT INTO public.languoid_alias ( + subject_languoid_id, + label_languoid_id, + name, + alias_type, + source_names +) +SELECT + 'fa8369a5-03a6-4d1e-ba44-152409a2f97c', + 'fa8369a5-03a6-4d1e-ba44-152409a2f97c', + 'မြန်မာ', + 'endonym'::public.alias_type, + ARRAY['lexvo'] +WHERE EXISTS ( + SELECT 1 FROM public.languoid WHERE id = 'fa8369a5-03a6-4d1e-ba44-152409a2f97c' +) +ON CONFLICT (subject_languoid_id, label_languoid_id, alias_type, name) DO NOTHING; + +-- Add Burmese exonymic alias (Myanmar) - only if the languoid exists +INSERT INTO public.languoid_alias ( + subject_languoid_id, + label_languoid_id, + name, + alias_type, + source_names +) +SELECT + 'fa8369a5-03a6-4d1e-ba44-152409a2f97c', + 'fa8369a5-03a6-4d1e-ba44-152409a2f97c', + 'Myanmar', + 'exonym'::public.alias_type, + ARRAY['lexvo'] +WHERE EXISTS ( + SELECT 1 FROM public.languoid WHERE id = 'fa8369a5-03a6-4d1e-ba44-152409a2f97c' +) +ON CONFLICT (subject_languoid_id, label_languoid_id, alias_type, name) DO NOTHING; + +-- ============================================================================ +-- Thai (ไทย) +-- ISO 639-3: tha +-- ============================================================================ + +-- Add Thai endonymic alias (ไทย) - only if the languoid exists +INSERT INTO public.languoid_alias ( + subject_languoid_id, + label_languoid_id, + name, + alias_type, + source_names +) +SELECT + 'fe361436-f7ff-4955-a442-59b76623b3c9', + 'fe361436-f7ff-4955-a442-59b76623b3c9', + 'ไทย', + 'endonym'::public.alias_type, + ARRAY['lexvo'] +WHERE EXISTS ( + SELECT 1 FROM public.languoid WHERE id = 'fe361436-f7ff-4955-a442-59b76623b3c9' +) +ON CONFLICT (subject_languoid_id, label_languoid_id, alias_type, name) DO NOTHING; + +-- ============================================================================ +-- Mandarin (普通话) +-- ISO 639-3: cmn +-- ============================================================================ + +-- Update name from "Mandarin Chinese" to "Mandarin" to match migration expectations +UPDATE public.languoid +SET name = 'Mandarin', last_updated = NOW() +WHERE id = '0d75d06f-2692-4127-b810-67dd64fa6eee' + AND name = 'Mandarin Chinese'; + +-- Add Mandarin endonymic aliases (普通话, 中文) - only if the languoid exists +INSERT INTO public.languoid_alias ( + subject_languoid_id, + label_languoid_id, + name, + alias_type, + source_names +) +SELECT + '0d75d06f-2692-4127-b810-67dd64fa6eee', + '0d75d06f-2692-4127-b810-67dd64fa6eee', + '普通话', + 'endonym'::public.alias_type, + ARRAY['lexvo'] +WHERE EXISTS ( + SELECT 1 FROM public.languoid WHERE id = '0d75d06f-2692-4127-b810-67dd64fa6eee' +) +ON CONFLICT (subject_languoid_id, label_languoid_id, alias_type, name) DO NOTHING; + +INSERT INTO public.languoid_alias ( + subject_languoid_id, + label_languoid_id, + name, + alias_type, + source_names +) +SELECT + '0d75d06f-2692-4127-b810-67dd64fa6eee', + '0d75d06f-2692-4127-b810-67dd64fa6eee', + '中文', + 'endonym'::public.alias_type, + ARRAY['lexvo'] +WHERE EXISTS ( + SELECT 1 FROM public.languoid WHERE id = '0d75d06f-2692-4127-b810-67dd64fa6eee' +) +ON CONFLICT (subject_languoid_id, label_languoid_id, alias_type, name) DO NOTHING; diff --git a/supabase/seeds/public.sql b/supabase/seeds/public.sql index 345a6ff6f..5ca3cd501 100644 --- a/supabase/seeds/public.sql +++ b/supabase/seeds/public.sql @@ -75,7 +75,11 @@ INSERT INTO "public"."languoid" ("id", "parent_id", "name", "level", "ui_ready", ('7a735df4-4f4e-4a03-b60e-eb7911152cf4', NULL, 'Nepali', 'language', true, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), ('a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d', NULL, 'Brazilian Portuguese', 'language', true, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), ('b2c3d4e5-f6a7-4b5c-9d0e-1f2a3b4c5d6e', NULL, 'Tok Pisin', 'language', true, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), - ('c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f', NULL, 'Indonesian', 'language', true, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL); + ('c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f', NULL, 'Indonesian', 'language', true, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('d4e5f6a7-b8c9-4d5e-0f1a-2b3c4d5e6f7a', NULL, 'Hindi', 'language', true, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('e5f6a7b8-c9d0-4e5f-1a2b-3c4d5e6f7a8b', NULL, 'Burmese', 'language', true, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('f6a7b8c9-d0e1-4f5a-2b3c-4d5e6f7a8b9c', NULL, 'Thai', 'language', true, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('a7b8c9d0-e1f2-4a5b-3c4d-5e6f7a8b9c0d', NULL, 'Mandarin', 'language', true, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL); -- @@ -91,7 +95,11 @@ INSERT INTO "public"."languoid_alias" ("id", "subject_languoid_id", "label_langu ('a1a1a1a1-0006-4000-8000-000000000006', '7a735df4-4f4e-4a03-b60e-eb7911152cf4', '7a735df4-4f4e-4a03-b60e-eb7911152cf4', 'नेपाली', 'endonym', ARRAY['lexvo'], true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), ('a1a1a1a1-0007-4000-8000-000000000007', 'a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d', 'a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d', 'Português Brasileiro', 'endonym', ARRAY['lexvo'], true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), ('a1a1a1a1-0008-4000-8000-000000000008', 'b2c3d4e5-f6a7-4b5c-9d0e-1f2a3b4c5d6e', 'b2c3d4e5-f6a7-4b5c-9d0e-1f2a3b4c5d6e', 'Tok Pisin', 'endonym', ARRAY['lexvo'], true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), - ('a1a1a1a1-0009-4000-8000-000000000009', 'c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f', 'c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f', 'Bahasa Indonesia', 'endonym', ARRAY['lexvo'], true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL); + ('a1a1a1a1-0009-4000-8000-000000000009', 'c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f', 'c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f', 'Bahasa Indonesia', 'endonym', ARRAY['lexvo'], true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('a1a1a1a1-0010-4000-8000-000000000010', 'd4e5f6a7-b8c9-4d5e-0f1a-2b3c4d5e6f7a', 'd4e5f6a7-b8c9-4d5e-0f1a-2b3c4d5e6f7a', 'हिन्दी', 'endonym', ARRAY['lexvo'], true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('a1a1a1a1-0011-4000-8000-000000000011', 'e5f6a7b8-c9d0-4e5f-1a2b-3c4d5e6f7a8b', 'e5f6a7b8-c9d0-4e5f-1a2b-3c4d5e6f7a8b', 'မြန်မာ', 'endonym', ARRAY['lexvo'], true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('a1a1a1a1-0012-4000-8000-000000000012', 'f6a7b8c9-d0e1-4f5a-2b3c-4d5e6f7a8b9c', 'f6a7b8c9-d0e1-4f5a-2b3c-4d5e6f7a8b9c', 'ไทย', 'endonym', ARRAY['lexvo'], true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('a1a1a1a1-0013-4000-8000-000000000013', 'a7b8c9d0-e1f2-4a5b-3c4d-5e6f7a8b9c0d', 'a7b8c9d0-e1f2-4a5b-3c4d-5e6f7a8b9c0d', '普通话', 'endonym', ARRAY['lexvo'], true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL); -- @@ -107,7 +115,11 @@ INSERT INTO "public"."languoid_source" ("id", "name", "version", "languoid_id", ('b2b2b2b2-0006-4000-8000-000000000006', 'iso639-3', NULL, '7a735df4-4f4e-4a03-b60e-eb7911152cf4', 'npi', NULL, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), ('b2b2b2b2-0007-4000-8000-000000000007', 'iso639-3', NULL, 'a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d', 'por', NULL, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), ('b2b2b2b2-0008-4000-8000-000000000008', 'iso639-3', NULL, 'b2c3d4e5-f6a7-4b5c-9d0e-1f2a3b4c5d6e', 'tpi', NULL, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), - ('b2b2b2b2-0009-4000-8000-000000000009', 'iso639-3', NULL, 'c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f', 'ind', NULL, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL); + ('b2b2b2b2-0009-4000-8000-000000000009', 'iso639-3', NULL, 'c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f', 'ind', NULL, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('b2b2b2b2-0010-4000-8000-000000000010', 'iso639-3', NULL, 'd4e5f6a7-b8c9-4d5e-0f1a-2b3c4d5e6f7a', 'hin', NULL, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('b2b2b2b2-0011-4000-8000-000000000011', 'iso639-3', NULL, 'e5f6a7b8-c9d0-4e5f-1a2b-3c4d5e6f7a8b', 'mya', NULL, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('b2b2b2b2-0012-4000-8000-000000000012', 'iso639-3', NULL, 'f6a7b8c9-d0e1-4f5a-2b3c-4d5e6f7a8b9c', 'tha', NULL, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL), + ('b2b2b2b2-0013-4000-8000-000000000013', 'iso639-3', NULL, 'a7b8c9d0-e1f2-4a5b-3c4d-5e6f7a8b9c0d', 'cmn', NULL, true, NULL, '2024-01-01 00:00:00+00', '2024-01-01 00:00:00+00', NULL); -- diff --git a/tsconfig.json b/tsconfig.json index 1691f3cf0..b3acf2972 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,11 +4,9 @@ "strict": true, "baseUrl": ".", "paths": { - "@/*": ["./*"], - "expo-file-system/next": ["node_modules/expo-file-system/build/next"] + "@/*": ["./*"] }, - "noUncheckedIndexedAccess": true, - "moduleResolution": "Bundler" + "noUncheckedIndexedAccess": true }, "include": [ "**/*.ts", @@ -22,6 +20,6 @@ "llm_supp_files", "node_modules", "supabase/functions/**/*", - "maestro/**/*" + ".maestro/**/*" ] } diff --git a/utils/audioWaveform.ts b/utils/audioWaveform.ts new file mode 100644 index 000000000..343b03598 --- /dev/null +++ b/utils/audioWaveform.ts @@ -0,0 +1,267 @@ +/** + * Extract waveform amplitude data from audio files. + * + * WAV files: Reads raw PCM samples, divides into `barCount` windows, + * computes RMS amplitude, and returns normalised 0–1 values. + * + * M4A files: Uses the native audio decoder (iOS/Android) to extract real + * PCM samples and compute RMS amplitudes. This avoids heuristics + * based on compressed frame sizes and produces meaningful waveforms. + */ + +import MicrophoneEnergyModule from '@/modules/microphone-energy'; +import { readFile } from '@/utils/fileUtils'; + +const waveformModule = MicrophoneEnergyModule as { + extractWaveform: ( + uri: string, + barCount: number, + normalize?: boolean + ) => Promise; +}; + +// --------------------------------------------------------------------------- +// WAV parser helpers +// --------------------------------------------------------------------------- + +interface WavInfo { + numChannels: number; + sampleRate: number; + bitsPerSample: number; + /** Byte offset where audio sample data starts */ + dataOffset: number; + /** Length in bytes of the audio sample data */ + dataLength: number; +} + +function parseWavHeader(buffer: ArrayBuffer): WavInfo { + const view = new DataView(buffer); + + // Verify RIFF header + const riff = + String.fromCharCode(view.getUint8(0)) + + String.fromCharCode(view.getUint8(1)) + + String.fromCharCode(view.getUint8(2)) + + String.fromCharCode(view.getUint8(3)); + if (riff !== 'RIFF') { + throw new Error('Not a valid WAV file (missing RIFF header)'); + } + + const wave = + String.fromCharCode(view.getUint8(8)) + + String.fromCharCode(view.getUint8(9)) + + String.fromCharCode(view.getUint8(10)) + + String.fromCharCode(view.getUint8(11)); + if (wave !== 'WAVE') { + throw new Error('Not a valid WAV file (missing WAVE identifier)'); + } + + let numChannels = 1; + let sampleRate = 44100; + let bitsPerSample = 16; + let dataOffset = 0; + let dataLength = 0; + + // Walk through chunks + let offset = 12; + while (offset < buffer.byteLength - 8) { + const chunkId = + String.fromCharCode(view.getUint8(offset)) + + String.fromCharCode(view.getUint8(offset + 1)) + + String.fromCharCode(view.getUint8(offset + 2)) + + String.fromCharCode(view.getUint8(offset + 3)); + const chunkSize = view.getUint32(offset + 4, true); // little-endian + + if (chunkId === 'fmt ') { + numChannels = view.getUint16(offset + 10, true); + sampleRate = view.getUint32(offset + 12, true); + bitsPerSample = view.getUint16(offset + 22, true); + } else if (chunkId === 'data') { + dataOffset = offset + 8; + dataLength = chunkSize; + break; // found what we need + } + + // Advance to next chunk (chunk header is 8 bytes + chunkSize, padded to even) + offset += 8 + chunkSize + (chunkSize % 2); + } + + if (dataOffset === 0 || dataLength === 0) { + throw new Error('WAV file missing data chunk'); + } + + return { numChannels, sampleRate, bitsPerSample, dataOffset, dataLength }; +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +/** + * Extract waveform data from a local audio file URI. + * Supports WAV files (direct parsing) and M4A/other formats (via Expo AV sampling). + * + * @param uri Local file URI (file:// or OPFS path) + * @param barCount Number of amplitude bars to return (default 128) + * @returns Array of normalised amplitudes (0–1), length === barCount + */ +export async function extractWaveformFromFile( + uri: string, + barCount = 128, + options?: { normalize?: boolean } +): Promise { + const normalize = options?.normalize ?? true; + // Try to read as WAV first + try { + const buffer = await readFile(uri); + return extractWaveformFromBuffer(buffer, barCount, normalize); + } catch (error) { + // If WAV parsing fails, try to extract from M4A/other formats using Expo AV + const errorMessage = error instanceof Error ? error.message : String(error); + if ( + errorMessage.includes('RIFF header') || + errorMessage.includes('WAV file') + ) { + // Not a WAV file - try M4A/other format extraction + return await extractWaveformFromAudioFile(uri, barCount, normalize); + } + // Re-throw if it's a different error (file not found, etc.) + throw error; + } +} + +// --------------------------------------------------------------------------- +// Native waveform extraction (M4A/AAC handled via platform decoders) +// --------------------------------------------------------------------------- + +/** + * Extract waveform data from an M4A (MPEG-4) file by reading AAC frame sizes + * from the sample size table (`stsz` atom) of the audio track. + * + * Improvements over naïve stsz parsing: + * 1. Finds the actual audio track (handler_type 'soun'), skipping metadata tracks. + * 2. Subtracts the baseline (5th percentile) frame size so silence → 0. + * 3. Applies a sqrt curve for better visual contrast. + */ +async function extractWaveformFromAudioFile( + uri: string, + barCount: number, + normalize: boolean +): Promise { + try { + const extract = waveformModule.extractWaveform; + // Backward compatible: native module may still accept only 2 args + if (extract.length >= 3) { + return await extract(uri, barCount, normalize); + } + return await extract(uri, barCount); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + if (errorMessage.includes('Received 3 arguments, but 2 was expected')) { + try { + return await waveformModule.extractWaveform(uri, barCount); + } catch (retryError) { + const retryMessage = + retryError instanceof Error ? retryError.message : String(retryError); + throw new Error( + `Failed to extract waveform from audio file: ${retryMessage}` + ); + } + } + throw new Error( + `Failed to extract waveform from audio file: ${errorMessage}` + ); + } +} + +/** + * Extract waveform data from a WAV ArrayBuffer. + */ +export function extractWaveformFromBuffer( + buffer: ArrayBuffer, + barCount = 128, + normalize = true +): number[] { + const { numChannels, bitsPerSample, dataOffset, dataLength } = + parseWavHeader(buffer); + + const bytesPerSample = bitsPerSample / 8; + const bytesPerFrame = bytesPerSample * numChannels; + const totalFrames = Math.floor(dataLength / bytesPerFrame); + + if (totalFrames === 0) { + return Array.from({ length: barCount }).fill(0); + } + + const view = new DataView(buffer); + const _framesPerBar = Math.max(1, Math.floor(totalFrames / barCount)); + const amplitudes: number[] = []; + + for (let bar = 0; bar < barCount; bar++) { + const startFrame = Math.floor((bar / barCount) * totalFrames); + const endFrame = Math.min( + totalFrames, + Math.floor(((bar + 1) / barCount) * totalFrames) + ); + const frameCount = endFrame - startFrame; + + if (frameCount === 0) { + amplitudes.push(0); + continue; + } + + let sumSquares = 0; + + for (let f = startFrame; f < endFrame; f++) { + const frameOffset = dataOffset + f * bytesPerFrame; + + // Average across channels + let frameSample = 0; + for (let ch = 0; ch < numChannels; ch++) { + const sampleOffset = frameOffset + ch * bytesPerSample; + + // Bounds check + if (sampleOffset + bytesPerSample > buffer.byteLength) break; + + let sample: number; + if (bitsPerSample === 16) { + sample = view.getInt16(sampleOffset, true) / 32768; + } else if (bitsPerSample === 8) { + // 8-bit WAV is unsigned (0–255), centre is 128 + sample = (view.getUint8(sampleOffset) - 128) / 128; + } else if (bitsPerSample === 24) { + // 24-bit signed, little-endian + const lo = view.getUint8(sampleOffset); + const mid = view.getUint8(sampleOffset + 1); + const hi = view.getInt8(sampleOffset + 2); // signed for sign extension + sample = ((hi << 16) | (mid << 8) | lo) / 8388608; + } else if (bitsPerSample === 32) { + sample = view.getFloat32(sampleOffset, true); + } else { + // Fallback – treat as 16-bit + sample = view.getInt16(sampleOffset, true) / 32768; + } + + frameSample += sample; + } + + frameSample /= numChannels; + sumSquares += frameSample * frameSample; + } + + const rms = Math.sqrt(sumSquares / frameCount); + amplitudes.push(rms); + } + + if (normalize) { + // Normalise so the peak bar is 1.0 + const maxAmplitude = Math.max(...amplitudes); + if (maxAmplitude > 0) { + for (let i = 0; i < amplitudes.length; i++) { + amplitudes[i] = amplitudes[i]! / maxAmplitude; + } + } + } + + return amplitudes; +} diff --git a/utils/backupUtils.ts b/utils/backupUtils.ts index 945a0e056..70683411e 100644 --- a/utils/backupUtils.ts +++ b/utils/backupUtils.ts @@ -8,8 +8,7 @@ import { import { AbstractSharedAttachmentQueue } from '@/db/powersync/AbstractSharedAttachmentQueue'; import type { System } from '@/db/powersync/system'; // Import System type import { eq, inArray, isNotNull } from 'drizzle-orm'; -import * as FileSystem from 'expo-file-system'; -import { StorageAccessFramework } from 'expo-file-system'; +import { Directory, File } from 'expo-file-system'; import { Platform } from 'react-native'; import { getDocumentDirectory, @@ -26,24 +25,28 @@ export async function requestBackupDirectory() { } try { - // Always request new permissions from the user + // Always request new permissions from the user via the new Directory picker console.log('Requesting directory permissions from user...'); - const permissions = - await StorageAccessFramework.requestDirectoryPermissionsAsync(); + const directory = await Directory.pickDirectoryAsync(); - if (permissions.granted && permissions.directoryUri) { - console.log('Directory permission granted:', permissions.directoryUri); + if (directory?.uri) { + console.log('Directory permission granted:', directory.uri); // We DO NOT save this to AsyncStorage anymore. // The user will be prompted each time. - return permissions.directoryUri; + return directory.uri; } else { console.log('Directory permission denied or URI missing.'); return null; // Permission denied or URI missing } } catch (dirError) { + // User cancelled or permission denied + const errorMessage = + dirError instanceof Error ? dirError.message : String(dirError); + if (errorMessage.includes('cancel') || errorMessage.includes('denied')) { + console.log('Directory picker cancelled by user.'); + return null; + } console.error('Error during directory permission request:', dirError); - // Optionally, inform the user with an Alert here - // RNAlert.alert('Error', 'Failed to get directory permissions.'); throw dirError; // Re-throw to be handled by the caller (e.g., backup/restore function) } } @@ -64,7 +67,7 @@ export function prepareBackupPaths(timestamp: string): { publishedDirName: `${mainBackupDirName}/published`, unpublishedDirName: `${mainBackupDirName}/unpublished`, csvFileName: `${mainBackupDirName}/asset_content_export.csv`, - dbSourceUri: (FileSystem.documentDirectory ?? '') + 'sqlite.db' + dbSourceUri: `${getDocumentDirectory() ?? ''}/sqlite.db` }; } @@ -111,60 +114,40 @@ function createCsvRow( // Helper to get all existing files across all backup directories recursively // This prevents duplicates across multiple backup runs -async function getAllExistingFiles( - baseDirectoryUri: string -): Promise> { +function getAllExistingFiles(baseDirectoryUri: string): Set { const existingFiles = new Set(); try { - // Read all entries in the base directory - const entries = - await StorageAccessFramework.readDirectoryAsync(baseDirectoryUri); + // Read all entries in the base directory using new Directory API + const baseDir = new Directory(baseDirectoryUri); + const entries = baseDir.list(); - for (const entryUri of entries) { + for (const entry of entries) { try { - const encoded = entryUri.split('/').pop()!; - const decodedSegment = decodeURIComponent(encoded); - const entryName = decodedSegment.includes('/') - ? decodedSegment.substring(decodedSegment.lastIndexOf('/') + 1) - : decodedSegment; + const entryName = entry.name; // Check if it's a backup directory (starts with "backup_") - if (entryName.startsWith('backup_')) { + if (entry instanceof Directory && entryName.startsWith('backup_')) { // Check published and unpublished subdirectories - const publishedPath = `${baseDirectoryUri}/${entryName}/published`; - const unpublishedPath = `${baseDirectoryUri}/${entryName}/unpublished`; - - try { - const publishedFiles = - await StorageAccessFramework.readDirectoryAsync(publishedPath); - for (const fileUri of publishedFiles) { - const fileEncoded = fileUri.split('/').pop()!; - const fileDecoded = decodeURIComponent(fileEncoded); - const fileName = fileDecoded.includes('/') - ? fileDecoded.substring(fileDecoded.lastIndexOf('/') + 1) - : fileDecoded; - existingFiles.add(fileName); + const collectFiles = (subDirName: string) => { + try { + const subDir = new Directory(entry.uri, subDirName); + if (subDir.exists) { + const files = subDir.list(); + for (const file of files) { + if (file instanceof File) { + existingFiles.add(file.name); + } + } + } + } catch { + // Directory might not exist, ignore } - } catch { - // Directory might not exist, ignore - } + }; - try { - const unpublishedFiles = - await StorageAccessFramework.readDirectoryAsync(unpublishedPath); - for (const fileUri of unpublishedFiles) { - const fileEncoded = fileUri.split('/').pop()!; - const fileDecoded = decodeURIComponent(fileEncoded); - const fileName = fileDecoded.includes('/') - ? fileDecoded.substring(fileDecoded.lastIndexOf('/') + 1) - : fileDecoded; - existingFiles.add(fileName); - } - } catch { - // Directory might not exist, ignore - } + collectFiles('published'); + collectFiles('unpublished'); } - } catch (error) { + } catch { // Skip entries that can't be read continue; } @@ -205,7 +188,7 @@ export async function backupUnsyncedAudio( ); // Get all existing files across all backup directories to avoid duplicates - const allExistingFiles = await getAllExistingFiles(baseDirectoryUri); + const allExistingFiles = getAllExistingFiles(baseDirectoryUri); // Query all asset_content_link records with audio // Include source asset information to show translation relationships @@ -357,14 +340,12 @@ export async function backupUnsyncedAudio( sourceUri = await getLocalAttachmentUriWithOPFS(audioId); } else { // Synced file: directly in shared_attachments - sourceUri = `${getDocumentDirectory()}${AbstractSharedAttachmentQueue.SHARED_DIRECTORY}/${cleanAudioId}`; + sourceUri = `${getDocumentDirectory()}/${AbstractSharedAttachmentQueue.SHARED_DIRECTORY}/${cleanAudioId}`; } try { - const fileInfo = await FileSystem.getInfoAsync(sourceUri, { - size: true - }); - if (!fileInfo.exists) { + const sourceFile = new File(sourceUri); + if (!sourceFile.exists) { console.warn( `[backupUnsyncedAudio] Source file not found: ${sourceUri}` ); @@ -396,27 +377,13 @@ export async function backupUnsyncedAudio( // Use proper mime type const mimeType = extension === 'm4a' ? 'audio/mp4' : 'audio/aac'; - // Create file in target folder - const backupFileUri = await StorageAccessFramework.createFileAsync( - targetFolder, - backupFileName, - mimeType - ); + // Create file in target folder using new Directory API + const targetDir = new Directory(targetFolder); + const backupFile = targetDir.createFile(backupFileName, mimeType); - // Read and write file - const fileContentBase64 = await FileSystem.readAsStringAsync( - sourceUri, - { - encoding: FileSystem.EncodingType.Base64 - } - ); - await FileSystem.writeAsStringAsync( - backupFileUri, - fileContentBase64, - { - encoding: FileSystem.EncodingType.Base64 - } - ); + // Read source as base64 and write to backup file + const fileContentBase64 = await sourceFile.base64(); + backupFile.write(fileContentBase64, { encoding: 'base64' }); // Track in existing files set to prevent duplicates allExistingFiles.add(backupFileName); @@ -442,31 +409,26 @@ export async function backupUnsyncedAudio( // Check if CSV already exists by looking for any CSV file in backup directories const existingCsvRows = new Set(); try { - const entries = - await StorageAccessFramework.readDirectoryAsync(baseDirectoryUri); - for (const entryUri of entries) { + const baseDir = new Directory(baseDirectoryUri); + const entries = baseDir.list(); + for (const entry of entries) { try { - const encoded = entryUri.split('/').pop()!; - const decodedSegment = decodeURIComponent(encoded); - const entryName = decodedSegment.includes('/') - ? decodedSegment.substring(decodedSegment.lastIndexOf('/') + 1) - : decodedSegment; + const entryName = entry.name; // Check if it's a backup directory - if (entryName.startsWith('backup_')) { + if (entry instanceof Directory && entryName.startsWith('backup_')) { // Look for CSV file inside this backup directory - const csvFileName = `${entryName}/asset_content_export.csv`; try { - const csvPath = `${baseDirectoryUri}/${csvFileName}`; - const csvContent = await FileSystem.readAsStringAsync(csvPath, { - encoding: FileSystem.EncodingType.UTF8 - }); - // Parse CSV and collect all data rows (skip header) - const lines = csvContent.split('\n'); - for (let i = 1; i < lines.length; i++) { - const line = lines[i]?.trim(); - if (line) { - existingCsvRows.add(line); + const csvFile = new File(entry.uri, 'asset_content_export.csv'); + if (csvFile.exists) { + const csvContent = await csvFile.text(); + // Parse CSV and collect all data rows (skip header) + const lines = csvContent.split('\n'); + for (let i = 1; i < lines.length; i++) { + const line = lines[i]?.trim(); + if (line) { + existingCsvRows.add(line); + } } } } catch { @@ -489,14 +451,9 @@ export async function backupUnsyncedAudio( }); const csvContent = newRows.join('\n'); - const csvUri = await StorageAccessFramework.createFileAsync( - baseDirectoryUri, - paths.csvFileName, - 'text/csv' - ); - await FileSystem.writeAsStringAsync(csvUri, csvContent, { - encoding: FileSystem.EncodingType.UTF8 - }); + const csvBaseDir = new Directory(baseDirectoryUri); + const csvFile = csvBaseDir.createFile(paths.csvFileName, 'text/csv'); + csvFile.write(csvContent); console.log( `[backupUnsyncedAudio] CSV exported with ${newRows.length} rows (${newRows.length - 1} new data rows)` ); @@ -510,19 +467,15 @@ export async function backupUnsyncedAudio( // Backup database if it exists try { const dbSourceUri = paths.dbSourceUri; - const dbInfo = await FileSystem.getInfoAsync(dbSourceUri); - if (dbInfo.exists) { - const dbBackupUri = await StorageAccessFramework.createFileAsync( - baseDirectoryUri, + const dbSourceFile = new File(dbSourceUri); + if (dbSourceFile.exists) { + const dbBackupDir = new Directory(baseDirectoryUri); + const dbBackupFile = dbBackupDir.createFile( paths.dbFullPathName, 'application/x-sqlite3' ); - const dbContent = await FileSystem.readAsStringAsync(dbSourceUri, { - encoding: FileSystem.EncodingType.Base64 - }); - await FileSystem.writeAsStringAsync(dbBackupUri, dbContent, { - encoding: FileSystem.EncodingType.Base64 - }); + const dbContent = await dbSourceFile.base64(); + dbBackupFile.write(dbContent, { encoding: 'base64' }); console.log('[backupUnsyncedAudio] Database backed up successfully'); } } catch (error) { diff --git a/utils/fileUtils.ts b/utils/fileUtils.ts index 6385cceff..1abe37886 100644 --- a/utils/fileUtils.ts +++ b/utils/fileUtils.ts @@ -1,6 +1,6 @@ import { AbstractSharedAttachmentQueue } from '@/db/powersync/AbstractSharedAttachmentQueue'; import { decode as decodeBase64 } from 'base64-arraybuffer'; -import * as FileSystem from 'expo-file-system'; +import { Directory, File, Paths } from 'expo-file-system'; import uuid from 'react-native-uuid'; /** @@ -51,40 +51,94 @@ export function getDirectory(uri: string) { /** * Delete a file if it exists. No-ops if uri is falsy or file is missing. */ -export async function deleteIfExists(uri: string | null | undefined) { - await FileSystem.deleteAsync(uri ?? '', { idempotent: true }); +export function deleteIfExists(uri: string | null | undefined) { + if (!uri) return; + try { + const file = new File(uri); + if (file.exists) { + file.delete(); + } + } catch { + // Idempotent - ignore errors (file may not exist or be a directory) + try { + const dir = new Directory(uri); + if (dir.exists) { + dir.delete(); + } + } catch { + // Silently ignore + } + } } -export async function getFileInfo(uri: string | null | undefined) { - return await FileSystem.getInfoAsync(uri ?? ''); +export function getFileInfo(uri: string | null | undefined) { + if (!uri) + return { exists: false as const, size: undefined, isDirectory: false }; + try { + const file = new File(uri); + if (file.exists) { + return { + exists: true as const, + size: file.size ?? undefined, + isDirectory: false, + uri + }; + } + const dir = new Directory(uri); + if (dir.exists) { + return { + exists: true as const, + size: dir.size ?? undefined, + isDirectory: true, + uri + }; + } + return { exists: false as const, size: undefined, isDirectory: false, uri }; + } catch { + return { exists: false as const, size: undefined, isDirectory: false, uri }; + } } /** * Check if a file exists at the given uri. */ -export async function fileExists(uri: string | null | undefined) { - const fileInfo = await getFileInfo(uri); - return fileInfo.exists; +export function fileExists(uri: string | null | undefined): boolean { + if (!uri) return false; + try { + const file = new File(uri); + return file.exists; + } catch { + return false; + } } -export async function ensureDir(uri: string) { - const directoryInfo = await FileSystem.getInfoAsync(uri); - if (!directoryInfo.exists || !directoryInfo.isDirectory) { - await FileSystem.makeDirectoryAsync(uri, { - intermediates: true - }); +export function ensureDir(uri: string) { + const dir = new Directory(uri); + if (!dir.exists) { + dir.create(); } } -export async function writeFile( +export function writeFile( fileURI: string, - base64Data: string, + data: string, options?: { encoding?: 'utf8' | 'base64' } ) { - const { encoding = FileSystem.EncodingType.UTF8 } = options ?? {}; + const { encoding = 'utf8' } = options ?? {}; const dir = getDirectory(fileURI); - await ensureDir(dir); - await FileSystem.writeAsStringAsync(fileURI, base64Data, { encoding }); + ensureDir(dir); + const file = new File(fileURI); + if (encoding === 'base64') { + // Decode base64 to bytes and write as binary + const binaryString = atob(data); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + file.write(bytes); + } else { + file.write(data); + } } /** @@ -124,48 +178,46 @@ export function normalizeFileUri(uri: string): string { return normalized; } -export async function moveFile(sourceUri: string, targetUri: string) { - // On iOS Simulator, FileSystem.moveAsync has a bug where it appends /.. to paths - // Workaround: Use copy + delete instead of move - // This is more reliable across platforms and avoids the simulator bug - +export function moveFile(sourceUri: string, targetUri: string) { // Normalize both URIs to ensure they're properly formatted const fromUri = normalizeFileUri(sourceUri); const toUri = normalizeFileUri(targetUri); + const sourceFile = new File(fromUri); + try { - // Try moveAsync first (faster on real devices) - await FileSystem.moveAsync({ from: fromUri, to: toUri }); + // Use the new File.move() API - handles move natively + sourceFile.move(new File(toUri)); } catch (error) { - // If moveAsync fails (e.g., iOS Simulator bug), fall back to copy + delete + // If move fails (e.g., iOS Simulator bug), fall back to copy + delete const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('/..') || errorMessage.includes('not writable')) { - console.log('⚠️ moveAsync failed, using copy + delete fallback'); + console.log('moveAsync failed, using copy + delete fallback'); // Copy the file - await FileSystem.copyAsync({ from: fromUri, to: toUri }); - // Delete the source file - ensure URI is normalized before deletion - const deleteUri = normalizeFileUri(fromUri); + const src = new File(fromUri); + src.copy(new File(toUri)); + // Delete the source file try { - await FileSystem.deleteAsync(deleteUri, { idempotent: true }); + const toDelete = new File(normalizeFileUri(fromUri)); + if (toDelete.exists) { + toDelete.delete(); + } } catch (deleteError) { - // If deletion fails (common on iOS Simulator with temp files), log but don't fail - // Temp files will be cleaned up automatically by the OS + const deleteUri = normalizeFileUri(fromUri); const isTempFile = deleteUri.includes('/tmp/') || deleteUri.includes('/tmp'); if (isTempFile) { console.log( - `⚠️ Failed to delete temp file (will be cleaned up automatically): ${deleteUri}` + `Failed to delete temp file (will be cleaned up automatically): ${deleteUri}` ); } else { - // For non-temp files, log the error but don't throw - file was already copied console.warn( - `⚠️ Failed to delete source file after copy: ${deleteUri}`, + `Failed to delete source file after copy: ${deleteUri}`, deleteError ); } } } else { - // Re-throw if it's a different error throw error; } } @@ -175,32 +227,33 @@ export async function readFile( fileURI: string, _options?: { encoding?: 'utf8' | 'base64' } ) { - if (!(await fileExists(fileURI))) { + const file = new File(fileURI); + if (!file.exists) { throw new Error(`File does not exist: ${fileURI}`); } // For binary files (audio, images, etc.), always read as base64 // This prevents data corruption from UTF-8 conversion - const fileContent = await FileSystem.readAsStringAsync(fileURI, { - encoding: FileSystem.EncodingType.Base64 - }); + const fileContent = await file.base64(); // Convert base64 to ArrayBuffer properly return base64ToArrayBuffer(fileContent); } -export async function deleteFile(uri: string) { - if (await fileExists(uri)) { - await FileSystem.deleteAsync(uri); +export function deleteFile(uri: string) { + const file = new File(uri); + if (file.exists) { + file.delete(); } } -export async function copyFile(sourceUri: string, targetUri: string) { - await FileSystem.copyAsync({ from: sourceUri, to: targetUri }); +export function copyFile(sourceUri: string, targetUri: string) { + const source = new File(sourceUri); + source.copy(new File(targetUri)); } export function getDocumentDirectory() { - return FileSystem.documentDirectory; + return Paths.document.uri; } const encoder = new TextEncoder(); @@ -214,7 +267,12 @@ export function base64ToArrayBuffer(base64: string) { } export function getLocalUri(filePath: string) { - return `${getDocumentDirectory()}${filePath}`; + const docDir = getDocumentDirectory(); + // Ensure single slash between directory and file path + const normalizedPath = filePath.startsWith('/') + ? filePath.slice(1) + : filePath; + return `${docDir}/${normalizedPath}`; } export function getLocalFilePathSuffix(filename: string): string { @@ -247,7 +305,7 @@ export async function saveAudioLocally(uri: string) { const extension = cleanSourceUri.split('.').pop() || 'wav'; const newUri = `local/${uuid.v4()}.${extension}`; - console.log('🔍 Saving audio file locally:', cleanSourceUri, newUri); + console.log('Saving audio file locally:', cleanSourceUri, newUri); // Initial delay to allow native module to finish writing the file // This is especially important for Android where the file write may not be fully flushed @@ -263,7 +321,7 @@ export async function saveAudioLocally(uri: string) { console.log(`🔍 Checking if file exists: ${cleanSourceUri}`); for (let attempt = 0; attempt < maxRetries; attempt++) { - fileExistsNow = await fileExists(cleanSourceUri); + fileExistsNow = fileExists(cleanSourceUri); if (fileExistsNow) { console.log(`✅ File found on attempt ${attempt + 1}/${maxRetries}`); break; @@ -271,7 +329,7 @@ export async function saveAudioLocally(uri: string) { if (attempt < maxRetries - 1) { console.log( - `⏳ File not found yet (attempt ${attempt + 1}/${maxRetries}), retrying in ${retryDelayMs}ms...` + `File not found yet (attempt ${attempt + 1}/${maxRetries}), retrying in ${retryDelayMs}ms...` ); await new Promise((resolve) => setTimeout(resolve, retryDelayMs)); } else { @@ -283,28 +341,17 @@ export async function saveAudioLocally(uri: string) { if (!fileExistsNow) { // Get file info for better error message - const fileInfo = await getFileInfo(cleanSourceUri); - const errorMsg = `File does not exist after ${maxRetries} attempts (${maxRetries * retryDelayMs}ms total): ${cleanSourceUri}. File info: ${JSON.stringify(fileInfo)}`; - console.error('❌', errorMsg); - - // Additional debugging: check if parent directory exists - const parentDir = getDirectory(cleanSourceUri); - console.error('❌ Checking parent directory:', parentDir); - try { - const dirInfo = await getFileInfo(parentDir); - console.error('❌ Parent directory info:', JSON.stringify(dirInfo)); - } catch (dirError) { - console.error('❌ Failed to get parent directory info:', dirError); - } - + const fileInfo = getFileInfo(cleanSourceUri); + const errorMsg = `File does not exist after ${maxRetries} attempts: ${cleanSourceUri}. File info: ${JSON.stringify(fileInfo)}`; + console.error(errorMsg); throw new Error(errorMsg); } const newPath = getLocalAttachmentUri(newUri); - await ensureDir(getDirectory(newPath)); + ensureDir(getDirectory(newPath)); // Debug: Log the URIs before moving - console.log('📦 Moving file:', { + console.log('Moving file:', { from: cleanSourceUri, to: newPath, fromLength: cleanSourceUri.length, @@ -312,20 +359,20 @@ export async function saveAudioLocally(uri: string) { }); try { - await moveFile(cleanSourceUri, newPath); + moveFile(cleanSourceUri, newPath); } catch (error) { // Enhanced error logging for debugging - const sourceStillExists = await fileExists(cleanSourceUri); - console.error('❌ moveFile error details:', { + console.error('moveFile error details:', { error, sourceUri: cleanSourceUri, targetUri: newPath, - sourceExists: sourceStillExists, - targetExists: await fileExists(newPath) + sourceExists: fileExists(cleanSourceUri), + targetExists: fileExists(newPath) }); // Fallback: try to copy instead of move // This can happen if the source file was already moved/deleted or has permission issues + const sourceStillExists = fileExists(cleanSourceUri); if (sourceStillExists) { console.log( '⚠️ Move failed but source exists, attempting copy as fallback...' @@ -345,6 +392,6 @@ export async function saveAudioLocally(uri: string) { } } - console.log('✅ Audio file saved locally:', getLocalAttachmentUri(newUri)); + console.log('Audio file saved locally:', getLocalAttachmentUri(newUri)); return newUri; } diff --git a/utils/localAudioConcat.ts b/utils/localAudioConcat.ts index f8eca3035..9314c24a3 100644 --- a/utils/localAudioConcat.ts +++ b/utils/localAudioConcat.ts @@ -15,7 +15,7 @@ import { normalizeFileUri } from '@/utils/fileUtils'; import { and, asc, eq, inArray, isNotNull, isNull } from 'drizzle-orm'; -import * as FileSystem from 'expo-file-system'; +import { File, Paths } from 'expo-file-system'; import * as Sharing from 'expo-sharing'; import { Platform } from 'react-native'; @@ -555,7 +555,8 @@ export async function concatenateAndShareQuestAudio( if (isWav) { // Convert .wav to .m4a - const tempM4aPath = `${FileSystem.cacheDirectory}temp_${Date.now()}_${i}.m4a`; + const cacheUri = Paths.cache.uri; + const tempM4aPath = `${cacheUri}/temp_${Date.now()}_${i}.m4a`; const tempM4aNativePath = getNativePath(tempM4aPath); tempFiles.push(tempM4aPath); console.log(`Converting ${nativePath} to ${tempM4aNativePath}...`); @@ -717,7 +718,8 @@ export async function concatenateAndShareQuestAudio( if (parts.length === 0) parts.push('quest'); const outputFileName = `${parts.join('-')}-${dateStr}.m4a`; - const outputPath = `${FileSystem.cacheDirectory}${outputFileName}`; + const cacheDir = Paths.cache.uri; + const outputPath = `${cacheDir}/${outputFileName}`; const outputNativePath = getNativePath(outputPath); // Convert audio URIs to the format expected by concatAudioFiles @@ -743,7 +745,10 @@ export async function concatenateAndShareQuestAudio( // Clean up temporary converted files for (const tempFile of tempFiles) { try { - await FileSystem.deleteAsync(tempFile, { idempotent: true }); + const file = new File(tempFile); + if (file.exists) { + file.delete(); + } } catch (error) { console.warn(`Failed to delete temp file ${tempFile}:`, error); } diff --git a/utils/recordingPool.ts b/utils/recordingPool.ts deleted file mode 100644 index e7eeff6cd..000000000 --- a/utils/recordingPool.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Recording Pool - Pre-warm recording objects for instant button response - * - * Problem: Creating Audio.Recording on button press takes 200-500ms - * Solution: Keep a pre-warmed recording ready, regenerate in background - * - * Impact: Button press → recording start: 300ms → <50ms - */ - -import { Audio } from 'expo-av'; - -class RecordingPoolManager { - private preparedRecording: Audio.Recording | null = null; - private isWarming = false; - private permissionsGranted = false; - private warmUpPromise: Promise | null = null; - - /** - * Pre-warm a recording object during idle time - * Call this on component mount or when user navigates to recording view - * - * Multiple calls are safe - they'll wait for existing warmup to complete - */ - async warmUp(): Promise { - // If already warming up, return the existing promise - if (this.warmUpPromise) { - return this.warmUpPromise; - } - - // Don't create multiple recordings simultaneously - if (this.preparedRecording) { - return; - } - - this.isWarming = true; - - // Create promise that all concurrent callers can wait on - this.warmUpPromise = this._warmUpInternal(); - - try { - await this.warmUpPromise; - } finally { - this.warmUpPromise = null; - } - } - - private async _warmUpInternal(): Promise { - try { - // Check permissions first - const permissionResponse = await Audio.getPermissionsAsync(); - - if (permissionResponse.status !== Audio.PermissionStatus.GRANTED) { - console.log('[RecordingPool] Permissions not granted, skipping warmup'); - this.isWarming = false; - return; - } - - this.permissionsGranted = true; - - // Set audio mode - await Audio.setAudioModeAsync({ - allowsRecordingIOS: true, - playsInSilentModeIOS: true - }); - - // Pre-create recording with high quality settings - const highQuality = Audio.RecordingOptionsPresets.HIGH_QUALITY; - const options = { - ...highQuality, - ios: { - ...(highQuality?.ios ?? {}), - isMeteringEnabled: true - }, - android: { - ...(highQuality?.android ?? {}), - isMeteringEnabled: true - } - } as typeof highQuality; - - const { recording } = await Audio.Recording.createAsync(options); - - this.preparedRecording = recording; - console.log('[RecordingPool] Recording pre-warmed and ready'); - } catch (error) { - console.error('[RecordingPool] Failed to warm up recording:', error); - } finally { - this.isWarming = false; - } - } - - /** - * Get a pre-warmed recording instantly (or create one if none ready) - * - * Note: Don't warm up the next recording yet! Wait until the current - * recording is actually started (via startAsync()) to avoid conflicts. - * Call warmUpNext() after starting the recording. - */ - async getRecording(): Promise { - // Fast path: pre-warmed recording is ready - if (this.preparedRecording) { - const recording = this.preparedRecording; - this.preparedRecording = null; - - // Don't warm up next recording yet - caller should do it after startAsync() - - return recording; - } - - // Slow path: no recording ready, create on demand - console.log( - '[RecordingPool] No recording ready, creating on demand (slower)' - ); - - // Ensure permissions - if (!this.permissionsGranted) { - const permissionResponse = await Audio.requestPermissionsAsync(); - if (permissionResponse.status !== Audio.PermissionStatus.GRANTED) { - throw new Error('Microphone permission denied'); - } - this.permissionsGranted = true; - } - - await Audio.setAudioModeAsync({ - allowsRecordingIOS: true, - playsInSilentModeIOS: true - }); - - const highQuality = Audio.RecordingOptionsPresets.HIGH_QUALITY; - const options = { - ...highQuality, - ios: { - ...(highQuality?.ios ?? {}), - isMeteringEnabled: true - }, - android: { - ...(highQuality?.android ?? {}), - isMeteringEnabled: true - } - } as typeof highQuality; - - const { recording } = await Audio.Recording.createAsync(options); - - // Don't warm up next one yet - caller should do it after startAsync() - - return recording; - } - - /** - * Warm up the next recording (call after starting the current one) - * Safe to call multiple times - will only warm up once - */ - warmUpNext(): void { - // Use setTimeout to defer warmup slightly after recording starts - setTimeout(() => { - void this.warmUp(); - }, 100); // Small delay ensures current recording is fully started - } - - /** - * Clean up any prepared recording (call on unmount) - */ - async cleanup(): Promise { - if (this.preparedRecording) { - try { - if (!this.preparedRecording._isDoneRecording) { - await this.preparedRecording.stopAndUnloadAsync(); - } - } catch (error) { - console.error('[RecordingPool] Cleanup error:', error); - } - this.preparedRecording = null; - } - } - - /** - * Request permissions explicitly (call early, like on app launch) - */ - async requestPermissions(): Promise { - try { - const permissionResponse = await Audio.requestPermissionsAsync(); - this.permissionsGranted = - permissionResponse.status === Audio.PermissionStatus.GRANTED; - return this.permissionsGranted; - } catch (error) { - console.error('[RecordingPool] Permission request error:', error); - return false; - } - } - - /** - * Check if a recording is ready to use - */ - isReady(): boolean { - return this.preparedRecording !== null && this.permissionsGranted; - } -} - -// Export singleton instance -export const recordingPool = new RecordingPoolManager(); diff --git a/utils/restoreUtils.ts b/utils/restoreUtils.ts index 1efd487c2..2992b50b7 100644 --- a/utils/restoreUtils.ts +++ b/utils/restoreUtils.ts @@ -1,6 +1,5 @@ import RNAlert from '@blazejkustra/react-native-alert'; -import * as FileSystem from 'expo-file-system'; -import { StorageAccessFramework } from 'expo-file-system'; +import { Directory, File, Paths } from 'expo-file-system'; import { Platform } from 'react-native'; // import * as SQLite from 'expo-sqlite/legacy'; // Removed SQLite import import type { System } from '@/db/powersync/system'; // actual System instance type @@ -140,9 +139,11 @@ async function restoreFromBackup( // let backupDb: SQLite.WebSQLDatabase | null = null; try { - // Read available files first - const fileUris = - await StorageAccessFramework.readDirectoryAsync(backupDirectoryUri); + // Read available files first using new Directory API + const backupDir = new Directory(backupDirectoryUri); + const dirEntries = backupDir.list(); + // Get file URIs for backward compatibility with rest of the function + const fileUris = dirEntries.map((entry) => entry.uri); console.log( `[restoreFromBackup] Files in backup directory: ${fileUris.join(', ')}` ); @@ -164,13 +165,13 @@ async function restoreFromBackup( if (options.restoreAudio) { // This will always be true now console.log('[restoreFromBackup] Starting audio file restore'); - const localAttachmentsDir = - (FileSystem.documentDirectory ?? '') + - `${AbstractSharedAttachmentQueue.SHARED_DIRECTORY}/`; // Target shared_attachments + const docDir = Paths.document.uri; + const localAttachmentsDir = `${docDir}/${AbstractSharedAttachmentQueue.SHARED_DIRECTORY}/`; // Target shared_attachments try { - await FileSystem.makeDirectoryAsync(localAttachmentsDir, { - intermediates: true - }); + const attachmentsDirectory = new Directory(localAttachmentsDir); + if (!attachmentsDirectory.exists) { + attachmentsDirectory.create({ intermediates: true }); + } } catch (e) { console.warn( '[restoreFromBackup] Failed to ensure attachments directory, may already exist:', @@ -184,13 +185,9 @@ async function restoreFromBackup( // Report initial progress callbacks?.onProgress?.(0, totalFiles); - for (const [index, fileUri] of fileUris.entries()) { - const encoded = fileUri.split('/').pop()!; - const decodedSegment = decodeURIComponent(encoded); - // Extract the actual filename after the last '/' if present - const fileName = decodedSegment.includes('/') - ? decodedSegment.substring(decodedSegment.lastIndexOf('/') + 1) - : decodedSegment; + for (const [index, entry] of dirEntries.entries()) { + const fileUri = entry.uri; + const fileName = entry.name; // 1. Extract Asset ID (first 36 chars, should be a UUID) if (fileName.length < 36 + 1 + 1 + 1) { @@ -308,16 +305,10 @@ async function restoreFromBackup( } const targetLanguageId = projectRecord.target_language_id; - const contentBase64 = await StorageAccessFramework.readAsStringAsync( - fileUri, - { - encoding: FileSystem.EncodingType.Base64 - } - ); - const tempFileUri = (FileSystem.cacheDirectory ?? '') + fileName; - await FileSystem.writeAsStringAsync(tempFileUri, contentBase64, { - encoding: FileSystem.EncodingType.Base64 - }); + const sourceFile = new File(fileUri); + const tempFile = new File(Paths.cache, fileName); + sourceFile.copy(tempFile); + const tempFileUri = tempFile.uri; // Ensure attachment queues are ready before saving audio await system.ensureAttachmentQueuesReady(); if (!system.permAttachmentQueue) { diff --git a/views/AccountDeletionView.tsx b/views/AccountDeletionView.tsx index b5168342d..f5a9c024b 100644 --- a/views/AccountDeletionView.tsx +++ b/views/AccountDeletionView.tsx @@ -123,6 +123,7 @@ export default function AccountDeletionView() { { text: t('deleteAccount'), style: 'destructive', + isPreferred: true, onPress: () => { void deleteAccount(); } diff --git a/views/CorruptedAttachmentsView.tsx b/views/CorruptedAttachmentsView.tsx index e3a3205e8..413afe3fc 100644 --- a/views/CorruptedAttachmentsView.tsx +++ b/views/CorruptedAttachmentsView.tsx @@ -83,6 +83,7 @@ export default function CorruptedAttachmentsView() { { text: t('clean'), style: 'destructive', + isPreferred: true, onPress: async () => { try { setCleaningIds((prev) => new Set(prev).add(attachmentId)); @@ -137,6 +138,7 @@ export default function CorruptedAttachmentsView() { { text: t('clean'), style: 'destructive', + isPreferred: true, onPress: async () => { try { setCleaningAll(true); diff --git a/views/ForgotPasswordView.tsx b/views/ForgotPasswordView.tsx index 984eef94c..8b7bef81a 100644 --- a/views/ForgotPasswordView.tsx +++ b/views/ForgotPasswordView.tsx @@ -59,6 +59,7 @@ export default function ForgotPasswordView({ RNAlert.alert(t('success'), t('checkEmailForResetLink'), [ { text: t('ok'), + isPreferred: true, onPress: () => safeNavigate(() => onNavigate('sign-in', { email: form.getValues('email') }) @@ -134,7 +135,7 @@ export default function ForgotPasswordView({ ) } disabled={isPending} - variant="link" + variant="plain" > {t('backToLogin')} diff --git a/views/NotificationsView.tsx b/views/NotificationsView.tsx index b8eb55754..7478e7537 100644 --- a/views/NotificationsView.tsx +++ b/views/NotificationsView.tsx @@ -39,14 +39,13 @@ import { useAppNavigation } from '@/hooks/useAppNavigation'; import { useLocalization } from '@/hooks/useLocalization'; import { useNetworkStatus } from '@/hooks/useNetworkStatus'; import { useLocalStore } from '@/store/localStore'; -import { colors } from '@/styles/theme'; -import { getThemeColor } from '@/utils/styleUtils'; import { useHybridData } from '@/views/new/useHybridData'; import RNAlert from '@blazejkustra/react-native-alert'; import { toCompilableQuery } from '@powersync/drizzle-driver'; import { useQueryClient } from '@tanstack/react-query'; import { and, eq, inArray, or } from 'drizzle-orm'; import { + AlertTriangle, BellIcon, CheckIcon, HomeIcon, @@ -57,12 +56,7 @@ import { XIcon } from 'lucide-react-native'; import React, { useState } from 'react'; -import { - ActivityIndicator, - RefreshControl, - ScrollView, - View -} from 'react-native'; +import { RefreshControl, ScrollView, View } from 'react-native'; interface NotificationItem { id: string; @@ -889,7 +883,7 @@ export default function NotificationsView() { {item.type === 'invite' @@ -953,7 +947,7 @@ export default function NotificationsView() { {!shouldDownload && ( - + {t('projectNotAvailableOfflineWarning')} @@ -966,7 +960,7 @@ export default function NotificationsView() { diff --git a/views/new/AssetCardItem.tsx b/views/new/AssetCardItem.tsx index aaca53c67..a87634d8b 100644 --- a/views/new/AssetCardItem.tsx +++ b/views/new/AssetCardItem.tsx @@ -16,6 +16,7 @@ import { useAppNavigation } from '@/hooks/useAppNavigation'; import { useLocalization } from '@/hooks/useLocalization'; // import { useTagStore } from '@/hooks/useTagStore'; import { SHOW_DEV_ELEMENTS } from '@/utils/featureFlags'; +import { cn } from '@/utils/styleUtils'; import type { AttachmentRecord } from '@powersync/attachments'; import { CheckSquareIcon, @@ -32,6 +33,7 @@ import { import React from 'react'; import { Pressable, View } from 'react-native'; // import { TagModal } from '../../components/TagModal'; +import { Button } from '@/components/ui/button'; import { Text } from '@/components/ui/text'; import { useItemDownload, useItemDownloadStatus } from './useHybridData'; @@ -286,13 +288,14 @@ const AssetCardItemComponent: React.FC = ({ return ( {/* Highlight indicator triangle */} {/* { isHighlighted && } */} @@ -398,7 +401,7 @@ const AssetCardItemComponent: React.FC = ({ */} {/* Actions: Edit name + Open details (hidden in selection mode) */} - + {/* Highlight indicator badge */} {isHighlighted && ( @@ -417,8 +420,8 @@ const AssetCardItemComponent: React.FC = ({ e.stopPropagation(); onRename(asset.id, asset.name); }} - className="flex h-7 w-7 items-center justify-center rounded-full bg-primary/20 active:bg-primary/40" - hitSlop={8} + className="flex size-7 items-center justify-center rounded-full bg-primary/20 active:bg-primary/40" + hitSlop={6} > = ({ isFlaggedForDownload={isDownloaded} isLoading={isDownloading} onPress={handleDownloadToggle} - size={16} + size={20} iconColor="text-primary/50" /> )} {!isSelectionMode && ( - { e.stopPropagation(); handleOpenAsset(); }} - className="mr-2" - hitSlop={8} + hitSlop={2} > - + )} diff --git a/views/new/AssetListItem.tsx b/views/new/AssetListItem.tsx index 79a06c4f2..836559b8a 100644 --- a/views/new/AssetListItem.tsx +++ b/views/new/AssetListItem.tsx @@ -28,6 +28,7 @@ import { import React from 'react'; import { Pressable, View } from 'react-native'; // import { TagModal } from '../../components/TagModal'; +import { Button } from '@/components/ui/button'; import { useItemDownload, useItemDownloadStatus } from './useHybridData'; // Define props locally to avoid require cycle @@ -240,16 +241,24 @@ export const AssetListItem: React.FC = ({ isFlaggedForDownload={isDownloaded} isLoading={isDownloading} onPress={handleDownloadToggle} - size={16} + size={20} iconColor="text-primary/50" /> - + {SHOW_DEV_ELEMENTS && ( diff --git a/views/new/BibleAssetListItem.tsx b/views/new/BibleAssetListItem.tsx index 728a5b472..2c0e96731 100644 --- a/views/new/BibleAssetListItem.tsx +++ b/views/new/BibleAssetListItem.tsx @@ -19,6 +19,7 @@ import { useAppNavigation } from '@/hooks/useAppNavigation'; import { useLocalization } from '@/hooks/useLocalization'; // import { useTagStore } from '@/hooks/useTagStore'; import { SHOW_DEV_ELEMENTS } from '@/utils/featureFlags'; +import { cn } from '@/utils/styleUtils'; import type { AttachmentRecord } from '@powersync/attachments'; import { CheckSquareIcon, @@ -35,6 +36,7 @@ import { import React from 'react'; import { Pressable, View } from 'react-native'; // import { TagModal } from '../../components/TagModal'; +import { Button } from '@/components/ui/button'; import { Text } from '@/components/ui/text'; import { useItemDownload, useItemDownloadStatus } from './useHybridData'; @@ -289,13 +291,14 @@ const BibleAssetListItemComponent: React.FC = ({ return ( {/* Highlight indicator triangle */} {/* { isHighlighted && } */} @@ -434,26 +437,27 @@ const BibleAssetListItemComponent: React.FC = ({ isFlaggedForDownload={isDownloaded} isLoading={isDownloading} onPress={handleDownloadToggle} - size={16} + size={20} iconColor="text-primary/50" /> )} {!isSelectionMode && ( - { e.stopPropagation(); handleOpenAsset(); }} - className="mr-2" - hitSlop={8} + hitSlop={2} > - + )} diff --git a/views/new/BibleAssetsView.tsx b/views/new/BibleAssetsView.tsx index 7fa274d62..3cfee4f93 100644 --- a/views/new/BibleAssetsView.tsx +++ b/views/new/BibleAssetsView.tsx @@ -11,7 +11,7 @@ import { SpeedDialTrigger } from '@/components/ui/speed-dial'; import { Text } from '@/components/ui/text'; -import { useAudio } from '@/contexts/AudioContext'; +import { useAssetAudio } from '@/services/assetAudio'; import { useAuth } from '@/contexts/AuthContext'; import { LayerType, useStatusContext } from '@/contexts/StatusContext'; import type { asset } from '@/db/drizzleSchema'; @@ -34,7 +34,6 @@ import { useLocalStore } from '@/store/localStore'; import { SHOW_DEV_ELEMENTS } from '@/utils/featureFlags'; import RNAlert from '@blazejkustra/react-native-alert'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { Audio } from 'expo-av'; import { BookmarkPlusIcon, BrushCleaning, @@ -53,7 +52,13 @@ import { UserPlusIcon } from 'lucide-react-native'; import React from 'react'; -import { ActivityIndicator, Pressable, View } from 'react-native'; +import { + ActivityIndicator, + InteractionManager, + Pressable, + View +} from 'react-native'; +import type { FlatList } from 'react-native'; import Animated, { cancelAnimation, Easing, @@ -94,13 +99,11 @@ import { } from '@/database_services/assetService'; import { audioSegmentService } from '@/database_services/audioSegmentService'; import { createQuestRecordingSession } from '@/database_services/questService'; -import { AppConfig } from '@/db/supabase/AppConfig'; import { useAssetsByQuest, useLocalAssetsByQuest } from '@/hooks/db/useAssets'; import { useBlockedAssetsCount } from '@/hooks/useBlockedCount'; import { useQuestOffloadVerification } from '@/hooks/useQuestOffloadVerification'; import { useHasUserReported } from '@/hooks/useReports'; import { resolveTable } from '@/utils/dbUtils'; -import { fileExists, getLocalAttachmentUriWithOPFS } from '@/utils/fileUtils'; import { publishQuest as publishQuestUtils } from '@/utils/publishUtils'; import { offloadQuest } from '@/utils/questOffloadUtils'; import { getThemeColor } from '@/utils/styleUtils'; @@ -457,7 +460,7 @@ export default function BibleAssetsView() { } = useCurrentNavigation(); const { goBack, navigate } = useAppNavigation(); const { currentUser } = useAuth(); - const audioContext = useAudio(); + const assetAudio = useAssetAudio(); const queryClient = useQueryClient(); const insets = useSafeAreaInsets(); @@ -564,17 +567,13 @@ export default function BibleAssetsView() { }, [] ); - // Track which asset is currently playing during play-all - const [currentlyPlayingAssetId, setCurrentlyPlayingAssetId] = React.useState< - string | null - >(null); // Track if PlayAll is running (for button icon state) const [isPlayAllRunning, setIsPlayAllRunning] = React.useState(false); - // OLD handlePlayAllAssets refs - commented out - // const assetUriMapRef = React.useRef>(new Map()); // URI -> assetId - // const assetOrderRef = React.useRef([]); // Ordered list of asset IDs - // const uriOrderRef = React.useRef([]); // Ordered list of URIs matching assetOrderRef - // const segmentDurationsRef = React.useRef([]); // Duration of each URI segment in ms + // Track active asset during PlayAll to avoid highlight gaps between clips. + const [playAllActiveAssetId, setPlayAllActiveAssetId] = React.useState< + string | null + >(null); + const listRef = React.useRef | null>(null); const fixedItemsIndexesRef = React.useRef([0]); // Ref to allow handlePlayAsset to be used in renderItem before it's defined const handlePlayAssetRef = React.useRef< @@ -605,27 +604,29 @@ export default function BibleAssetsView() { type Quest = typeof questTable.$inferSelect; // Use passed quest data if available (instant!), otherwise query - const { data: queriedQuestData, refetch: refetchQuest } = useHybridData({ - dataType: 'current-quest', - queryKeyParams: [currentQuestId], - offlineQuery: toCompilableQuery( - system.db.query.quest.findFirst({ - where: eq(questTable.id, currentQuestId!) - }) - ), - cloudQueryFn: async () => { - const { data, error } = await system.supabaseConnector.client - .from('quest') - .select('*') - .eq('id', currentQuestId) - .overrideTypes(); - if (error) throw error; - return data; - }, - enableCloudQuery: !!currentQuestId, - enableOfflineQuery: !!currentQuestId, - getItemId: (item) => item.id - }); + const { data: queriedQuestData, refetch: refetchQuest } = + useHybridData({ + dataType: 'current-quest', + queryKeyParams: [currentQuestId], + offlineQuery: toCompilableQuery( + system.db.query.quest.findMany({ + where: eq(questTable.id, currentQuestId!), + limit: 1 + }) + ), + cloudQueryFn: async () => { + const { data, error } = await system.supabaseConnector.client + .from('quest') + .select('*') + .eq('id', currentQuestId) + .overrideTypes(); + if (error) throw error; + return data; + }, + enableCloudQuery: !!currentQuestId, + enableOfflineQuery: !!currentQuestId, + getItemId: (item) => item.id + }); // Prefer queried data (fresh) over navigation data (may be stale) // This ensures UI updates immediately after publishing without needing to navigate away @@ -731,13 +732,16 @@ export default function BibleAssetsView() { }, [selectedQuest, currentBookId]); // Query project data to get privacy status if not passed - const { data: queriedProjectData } = useHybridData({ + const { data: queriedProjectData } = useHybridData< + Pick + >({ dataType: 'project-privacy-assets', queryKeyParams: [currentProjectId], offlineQuery: toCompilableQuery( - system.db.query.project.findFirst({ + system.db.query.project.findMany({ where: eq(project.id, currentProjectId!), - columns: { id: true, private: true, creator_id: true } + columns: { id: true, private: true, creator_id: true }, + limit: 1 }) ), cloudQueryFn: async () => { @@ -1118,6 +1122,7 @@ export default function BibleAssetsView() { { text: 'Delete', style: 'destructive', + isPreferred: true, onPress: () => { void (async () => { try { @@ -1167,6 +1172,7 @@ export default function BibleAssetsView() { { text: 'Merge', style: 'destructive', + isPreferred: true, onPress: () => { void (async () => { try { @@ -1224,7 +1230,7 @@ export default function BibleAssetsView() { ); await updateContentLinkOrder( target.id, - allContent.map((c) => c.id), + allContent.map((c: { id: string }) => c.id), { localOverride: true } ); @@ -2024,7 +2030,7 @@ export default function BibleAssetsView() { // Use memo key instead of Map reference for stable dependencies (always 1 string) }, [safeAttachmentStates]); - const handleAssetUpdate = React.useCallback(async () => { + const _handleAssetUpdate = React.useCallback(async () => { // await queryClient.invalidateQueries({ // // queryKey: ['assets', 'by-quest', currentQuestId], // queryKey: ['by-quest', currentQuestId], @@ -2455,16 +2461,14 @@ export default function BibleAssetsView() { // Handle asset items const asset = item.content; - const isPlaying = - (audioContext.isPlaying && - (audioContext.currentAudioId === asset.id || - (audioContext.currentAudioId === PLAY_ALL_AUDIO_ID && - currentlyPlayingAssetId === asset.id))) || - currentlyPlayingAssetId === asset.id; + const isPlaying = isPlayAllRunning + ? playAllActiveAssetId === asset.id + : assetAudio.isPlaying && assetAudio.currentAudioId === asset.id; const isSelected = selectedAssetIds.has(asset.id); const isAssetSelectedForRecording = + !isPlayAllRunning && !isPublished && selectedForRecording?.type === 'asset' && selectedForRecording?.assetId === asset.id; @@ -2515,9 +2519,10 @@ export default function BibleAssetsView() { [ isPublished, currentQuestId, - audioContext.isPlaying, - audioContext.currentAudioId, - currentlyPlayingAssetId, + assetAudio.isPlaying, + assetAudio.currentAudioId, + isPlayAllRunning, + playAllActiveAssetId, stableOnPlay, getRangeForAsset, isSelectionMode, @@ -2575,368 +2580,62 @@ export default function BibleAssetsView() { currentQuestId ); - // Special audio ID for "play all" mode - const PLAY_ALL_AUDIO_ID = 'play-all-assets'; - - // Fetch audio URIs for an asset (similar to RecordingViewSimplified) - // Includes fallback logic for local-only files when server records are removed - const getAssetAudioUris = React.useCallback( - async (assetId: string): Promise => { - try { - // Get content links from both synced and local tables - const assetContentLinkSynced = resolveTable('asset_content_link', { - localOverride: false - }); - const contentLinksSynced = await system.db - .select() - .from(assetContentLinkSynced) - .where(eq(assetContentLinkSynced.asset_id, assetId)); - - const assetContentLinkLocal = resolveTable('asset_content_link', { - localOverride: true - }); - const contentLinksLocal = await system.db - .select() - .from(assetContentLinkLocal) - .where(eq(assetContentLinkLocal.asset_id, assetId)); - - // Prefer synced links, but merge with local for fallback - const allContentLinks = [...contentLinksSynced, ...contentLinksLocal]; - - // Deduplicate by ID (prefer synced over local) - const seenIds = new Set(); - const uniqueLinks = allContentLinks.filter((link) => { - if (seenIds.has(link.id)) { - return false; - } - seenIds.add(link.id); - return true; - }); - - if (uniqueLinks.length === 0) { - return []; - } - - // Get audio values from content links (can be URIs or attachment IDs) - const audioValues = uniqueLinks - .flatMap((link) => { - const audioArray = link.audio ?? []; - return audioArray; - }) - .filter((value): value is string => !!value); - - if (audioValues.length === 0) { - return []; - } - - // Process each audio value - can be either a local URI or an attachment ID - const uris: string[] = []; - for (const audioValue of audioValues) { - // Check if this is already a local URI (starts with 'local/' or 'file://') - if (audioValue.startsWith('local/')) { - // It's a direct local URI from saveAudioLocally() - const constructedUri = - await getLocalAttachmentUriWithOPFS(audioValue); - // Check if file exists at constructed path - if (await fileExists(constructedUri)) { - uris.push(constructedUri); - } else { - // File doesn't exist at expected path - try to find it in attachment queue - console.log( - `⚠️ Local URI ${audioValue} not found at ${constructedUri}, searching attachment queue...` - ); - - if (system.permAttachmentQueue) { - // Extract filename from local path (e.g., "local/uuid.wav" -> "uuid.wav") - const filename = audioValue.replace(/^local\//, ''); - // Extract UUID part (without extension) for more flexible matching - const uuidPart = filename.split('.')[0]; - - // Search attachment queue by filename or UUID - let attachment = await system.powersync.getOptional<{ - id: string; - filename: string | null; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE filename = ? OR filename LIKE ? OR id = ? OR id LIKE ? LIMIT 1`, - [filename, `%${uuidPart}%`, filename, `%${uuidPart}%`] - ); + // Ref to track if handlePlayAll is running (for cancellation and to avoid state conflicts) + const isPlayAllRunningRef = React.useRef(false); - // If not found, try searching all attachments for this asset's content links - if (!attachment && uniqueLinks.length > 0) { - const allAttachmentIds = uniqueLinks - .flatMap((link) => link.audio ?? []) - .filter( - (av): av is string => - typeof av === 'string' && - !av.startsWith('local/') && - !av.startsWith('file://') - ); - if (allAttachmentIds.length > 0) { - const placeholders = allAttachmentIds - .map(() => '?') - .join(','); - attachment = await system.powersync.getOptional<{ - id: string; - filename: string | null; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE id IN (${placeholders}) LIMIT 1`, - allAttachmentIds - ); - } - } + const stopPlayAll = React.useCallback(async () => { + isPlayAllRunningRef.current = false; + setIsPlayAllRunning(false); + setPlayAllActiveAssetId(null); + await assetAudio.stop(); + }, [assetAudio]); - if (attachment?.local_uri) { - const foundUri = system.permAttachmentQueue.getLocalUri( - attachment.local_uri - ); - // Verify the found file actually exists - if (await fileExists(foundUri)) { - uris.push(foundUri); - console.log( - `✅ Found attachment in queue for local URI ${audioValue.slice(0, 20)}` - ); - } else { - console.warn( - `⚠️ Attachment found in queue but file doesn't exist: ${foundUri}` - ); - } - } else { - // Try fallback to local table for alternative audio values - const fallbackLink = contentLinksLocal.find( - (link) => link.asset_id === assetId - ); - if (fallbackLink?.audio) { - for (const fallbackAudioValue of fallbackLink.audio) { - if (fallbackAudioValue.startsWith('file://')) { - if (await fileExists(fallbackAudioValue)) { - uris.push(fallbackAudioValue); - console.log(`✅ Found fallback file URI`); - break; - } - } - } - } - } - } - } - } else if (audioValue.startsWith('file://')) { - // Already a full file URI - verify it exists - if (await fileExists(audioValue)) { - uris.push(audioValue); - } else { - console.warn(`File URI does not exist: ${audioValue}`); - // Try to find in attachment queue by extracting filename from path - if (system.permAttachmentQueue) { - const filename = audioValue.split('/').pop(); - if (filename) { - const attachment = await system.powersync.getOptional<{ - id: string; - filename: string | null; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE filename = ? OR id = ? LIMIT 1`, - [filename, filename] - ); + const scrollToAssetInPlayAll = React.useCallback( + (assetId: string) => { + const listIndex = listItems.findIndex( + (item) => item.type === 'asset' && item.content.id === assetId + ); + if (listIndex < 0) return; - if (attachment?.local_uri) { - const foundUri = system.permAttachmentQueue.getLocalUri( - attachment.local_uri - ); - if (await fileExists(foundUri)) { - uris.push(foundUri); - console.log(`✅ Found attachment in queue for file URI`); - } - } - } - } - } - } else { - // It's an attachment ID - look it up in the attachment queue - if (!system.permAttachmentQueue) { - // No attachment queue - try fallback to local table - const fallbackLink = contentLinksLocal.find( - (link) => link.asset_id === assetId - ); - if (fallbackLink?.audio) { - for (const fallbackAudioValue of fallbackLink.audio) { - if (fallbackAudioValue.startsWith('local/')) { - const fallbackUri = - await getLocalAttachmentUriWithOPFS(fallbackAudioValue); - if (await fileExists(fallbackUri)) { - uris.push(fallbackUri); - break; - } - } else if (fallbackAudioValue.startsWith('file://')) { - if (await fileExists(fallbackAudioValue)) { - uris.push(fallbackAudioValue); - break; - } - } - } - } - continue; + // Defer until interactions settle to improve reliability while reordering/list updates occur. + InteractionManager.runAfterInteractions(() => { + requestAnimationFrame(() => { + const list = listRef.current; + if (!list) return; + try { + if (typeof list.scrollToIndex === 'function') { + list.scrollToIndex({ + index: listIndex, + animated: true, + viewPosition: 0.3 + }); + return; } - - const attachment = await system.powersync.getOptional<{ - id: string; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE id = ?`, - [audioValue] - ); - - if (attachment?.local_uri) { - const localUri = system.permAttachmentQueue.getLocalUri( - attachment.local_uri - ); - if (await fileExists(localUri)) { - uris.push(localUri); - } - } else { - // Attachment ID not found in queue - try fallback to local table - console.log( - `⚠️ Attachment ID ${audioValue.slice(0, 8)} not found in queue, checking local table fallback...` - ); - - const fallbackLink = contentLinksLocal.find( - (link) => link.asset_id === assetId - ); - if (fallbackLink?.audio) { - for (const fallbackAudioValue of fallbackLink.audio) { - if (fallbackAudioValue.startsWith('local/')) { - const fallbackUri = - await getLocalAttachmentUriWithOPFS(fallbackAudioValue); - if (await fileExists(fallbackUri)) { - uris.push(fallbackUri); - console.log( - `✅ Found fallback local URI for attachment ${audioValue.slice(0, 8)}` - ); - break; - } - } else if (fallbackAudioValue.startsWith('file://')) { - if (await fileExists(fallbackAudioValue)) { - uris.push(fallbackAudioValue); - console.log( - `✅ Found fallback file URI for attachment ${audioValue.slice(0, 8)}` - ); - break; - } - } - } - } else { - // Try to get cloud URL if local not available - try { - if (!AppConfig.supabaseBucket) { - continue; - } - const { data } = system.supabaseConnector.client.storage - .from(AppConfig.supabaseBucket) - .getPublicUrl(audioValue); - if (data.publicUrl) { - uris.push(data.publicUrl); - } - } catch (error) { - console.error('Failed to get cloud audio URL:', error); - } - } + if (typeof list.scrollToOffset === 'function') { + list.scrollToOffset({ + offset: Math.max(0, listIndex * 72), + animated: true + }); } + } catch (error) { + console.warn('Failed to auto-scroll play-all asset:', error); } - } - - return uris; - } catch (error) { - console.error('Failed to fetch audio URIs:', error); - return []; - } + }); + }); }, - [] + [listItems] ); - // OLD handlePlayAllAssets - Asset ranges for play-all: maps each asset to its time range - // const assetTimeRangesRef = React.useRef< - // { assetId: string; startMs: number; endMs: number }[] - // >([]); - - // Calculate which asset should be highlighted based on position - // NOTE: This is only used for handlePlayAsset (individual) - // handlePlayAll (new function) controls currentlyPlayingAssetId directly - const derivedCurrentlyPlayingAssetId = React.useMemo(() => { - // Not playing at all - if (!audioContext.isPlaying) { - return null; - } - - // Playing a single asset (not play-all mode) - return directly - if (audioContext.currentAudioId !== PLAY_ALL_AUDIO_ID) { - return audioContext.currentAudioId; - } - - // OLD handlePlayAllAssets logic - commented out - // // Play-all mode (handlePlayAllAssets): Use time ranges if available - // const ranges = assetTimeRangesRef.current; - // if (ranges.length > 0) { - // const position = audioContext.position; - // for (const range of ranges) { - // if (position >= range.startMs && position < range.endMs) { - // return range.assetId; - // } - // } - // // Position beyond all ranges - return last asset - // return ranges[ranges.length - 1]?.assetId || null; - // } - - // // Fallback to first asset in order - // return assetOrderRef.current[0] || null; - - return null; - }, [ - audioContext.isPlaying, - audioContext.currentAudioId, - audioContext.position - ]); - - // Ref to track if handlePlayAll is running (for cancellation and to avoid state conflicts) - const isPlayAllRunningRef = React.useRef(false); - // Ref to track current playing sound for immediate cancellation - const currentPlayAllSoundRef = React.useRef(null); - - // Ref to hold latest audioContext for cleanup (avoids stale closure) - const audioContextCurrentRef = React.useRef(audioContext); - React.useEffect(() => { - audioContextCurrentRef.current = audioContext; - }, [audioContext]); - - // Update state only for handlePlayAsset and handlePlayAllAssets - // handlePlayAll controls state directly so we skip when it's running - React.useEffect(() => { - // Skip if handlePlayAll is controlling the state directly - if (isPlayAllRunningRef.current) { - return; - } - - // Only update if we're in audioContext-controlled playback mode - if ( - audioContext.isPlaying && - (audioContext.currentAudioId === PLAY_ALL_AUDIO_ID || - audioContext.currentAudioId) - ) { - setCurrentlyPlayingAssetId(derivedCurrentlyPlayingAssetId); - } else if (!audioContext.isPlaying && !audioContext.currentAudioId) { - // Clear highlight when audio finishes naturally - setCurrentlyPlayingAssetId(null); - } - }, [ - derivedCurrentlyPlayingAssetId, - audioContext.isPlaying, - audioContext.currentAudioId - ]); - - // Handle play all - plays all assets sequentially with direct asset-audio linking - // Uses assets that have isAssetSelectedForRecording={true} in BibleAssetListItem (determined by selectedForRecording) - // Takes selectedAsset as parameter to avoid recreating the function when selection changes + const activateAssetForPlayAll = React.useCallback( + async (assetId: string) => { + setPlayAllActiveAssetId(assetId); + await Promise.resolve(); + scrollToAssetInPlayAll(assetId); + }, + [scrollToAssetInPlayAll] + ); + // Handle play all - plays all assets sequentially via assetAudio service const handlePlayAll = React.useCallback( async ( selectedAsset?: { type: 'asset' | 'separator'; assetId?: string } | null @@ -2944,168 +2643,62 @@ export default function BibleAssetsView() { try { // Check if already playing - toggle to stop if (isPlayAllRunningRef.current) { - isPlayAllRunningRef.current = false; - setIsPlayAllRunning(false); - - // Stop current sound immediately - if (currentPlayAllSoundRef.current) { - try { - await currentPlayAllSoundRef.current.stopAsync(); - await currentPlayAllSoundRef.current.unloadAsync(); - currentPlayAllSoundRef.current = null; - } catch (error) { - console.error('Error stopping sound:', error); - } - } - - setCurrentlyPlayingAssetId(null); - console.log('⏸️ Stopped play all'); + await stopPlayAll(); return; } // Determine which assets to process based on selection state let assetsToProcess: AssetQuestLink[]; - // Priority 1: selectedForRecording (for unpublished quests with recording selection) if (selectedAsset?.type === 'asset' && selectedAsset?.assetId) { const selectedIndex = assets.findIndex( (a) => a.id === selectedAsset.assetId ); - if (selectedIndex >= 0) { - assetsToProcess = assets.slice(selectedIndex); - } else { - assetsToProcess = assets; - } - } - // Priority 2: selectedAssetIds (for published quests with visual selection) - else if (selectedAssetIds.size > 0) { + assetsToProcess = + selectedIndex >= 0 ? assets.slice(selectedIndex) : assets; + } else if (selectedAssetIds.size > 0) { const firstSelectedIndex = assets.findIndex((a) => selectedAssetIds.has(a.id) ); - if (firstSelectedIndex >= 0) { - assetsToProcess = assets.slice(firstSelectedIndex); - console.log( - `🎵 Starting from first selected asset at index ${firstSelectedIndex}` - ); - } else { - assetsToProcess = assets; - } - } - // Priority 3: No selection, play all - else { + assetsToProcess = + firstSelectedIndex >= 0 ? assets.slice(firstSelectedIndex) : assets; + } else { assetsToProcess = assets; } - if (assetsToProcess.length === 0) { - console.warn('⚠️ No assets to play'); - return; - } - - console.log( - `🎵 Starting play all from ${assetsToProcess.length} assets...` - ); + if (assetsToProcess.length === 0) return; - // Mark as running isPlayAllRunningRef.current = true; setIsPlayAllRunning(true); - // Build playlist: Array<{assetId, uris}> - const playlist: { assetId: string; uris: string[] }[] = []; - + // Play each asset sequentially. + // Keep highlight continuous across clip boundaries by controlling it here. for (const asset of assetsToProcess) { - // Check if cancelled - if (!isPlayAllRunningRef.current) { - console.log('⏸️ Play all cancelled during playlist build'); - return; - } - - // Get URIs for this asset (getAssetAudioUris handles all the resolution) - const uris = await getAssetAudioUris(asset.id); - if (uris.length > 0) { - playlist.push({ assetId: asset.id, uris }); - } - } - - if (playlist.length === 0) { - console.error('❌ No audio URIs found for any assets'); - isPlayAllRunningRef.current = false; - setIsPlayAllRunning(false); - return; - } - - console.log( - `▶️ Playing ${playlist.reduce((sum, p) => sum + p.uris.length, 0)} audio segments from ${playlist.length} assets` - ); - - // STEP 2: Play each asset sequentially with direct linking - for (let i = 0; i < playlist.length; i++) { - // Check if cancelled if (!isPlayAllRunningRef.current) { - console.log('⏸️ Play all cancelled'); - setCurrentlyPlayingAssetId(null); + setPlayAllActiveAssetId(null); return; } - const item = playlist[i]!; - - // HIGHLIGHT THIS ASSET - direct link! - setCurrentlyPlayingAssetId(item.assetId); - console.log( - `▶️ [${i + 1}/${playlist.length}] Playing asset ${item.assetId.slice(0, 8)} (${item.uris.length} segments)` - ); - - // Play all URIs for this asset sequentially - for (const uri of item.uris) { - // Check if cancelled - if (!isPlayAllRunningRef.current) { - setCurrentlyPlayingAssetId(null); - return; - } + await activateAssetForPlayAll(asset.id); + if (!isPlayAllRunningRef.current) break; - // Play this URI and wait for it to finish - await new Promise((resolve) => { - // Create and play the sound - Audio.Sound.createAsync({ uri }, { shouldPlay: true }) - .then(({ sound }) => { - // Store reference for immediate cancellation - currentPlayAllSoundRef.current = sound; - - // Set up listener for when sound finishes - sound.setOnPlaybackStatusUpdate((status) => { - if (!status.isLoaded) return; - - if (status.didJustFinish) { - currentPlayAllSoundRef.current = null; - void sound.unloadAsync().then(() => { - resolve(); - }); - } - }); - }) - .catch((error) => { - console.error('Failed to play audio:', error); - currentPlayAllSoundRef.current = null; - resolve(); // Continue to next even on error - }); - }); - } + await assetAudio.play(asset.id); + await assetAudio.waitForPlaybackEnd(asset.id); + if (!isPlayAllRunningRef.current) break; } - // Done playing all - console.log('✅ Finished playing all assets'); - setCurrentlyPlayingAssetId(null); + // Done isPlayAllRunningRef.current = false; setIsPlayAllRunning(false); - currentPlayAllSoundRef.current = null; + setPlayAllActiveAssetId(null); } catch (error) { - console.error('❌ Erro ao tocar todos os assets:', error); - setCurrentlyPlayingAssetId(null); + console.error('Failed to play all assets:', error); isPlayAllRunningRef.current = false; setIsPlayAllRunning(false); - currentPlayAllSoundRef.current = null; + setPlayAllActiveAssetId(null); } }, - [assets, getAssetAudioUris, selectedAssetIds] + [activateAssetForPlayAll, assets, assetAudio, selectedAssetIds, stopPlayAll] ); // Handle going to recording - stops any playing audio first @@ -3117,26 +2710,12 @@ export default function BibleAssetsView() { // Stop PlayAll if running if (isPlayAllRunningRef.current) { - isPlayAllRunningRef.current = false; - setIsPlayAllRunning(false); - - // Stop current sound immediately - if (currentPlayAllSoundRef.current) { - try { - await currentPlayAllSoundRef.current.stopAsync(); - await currentPlayAllSoundRef.current.unloadAsync(); - currentPlayAllSoundRef.current = null; - } catch (error) { - console.error('Error stopping sound:', error); - } - } - - setCurrentlyPlayingAssetId(null); + await stopPlayAll(); } - // Stop any other audio from audioContext - if (audioContext.isPlaying) { - await audioContext.stopCurrentSound(); + // Stop any playing audio + else if (assetAudio.isPlaying) { + await assetAudio.stop(); } // Navigate to recording view @@ -3161,7 +2740,8 @@ export default function BibleAssetsView() { } }); }, [ - audioContext, + assetAudio, + stopPlayAll, navigate, currentQuestId, currentProjectId, @@ -3175,171 +2755,45 @@ export default function BibleAssetsView() { limitVerse ]); + // Ref to hold latest assetAudio for cleanup (avoids stale closure) + const assetAudioRef = React.useRef(assetAudio); + React.useEffect(() => { + assetAudioRef.current = assetAudio; + }, [assetAudio]); + // Cleanup effect: Stop audio when component unmounts React.useEffect(() => { return () => { - // Stop audio playback if playing (access via ref for latest state) - if (audioContextCurrentRef.current.isPlaying) { - void audioContextCurrentRef.current.stopCurrentSound(); - } - - // Stop PlayAll if running - if (isPlayAllRunningRef.current) { - isPlayAllRunningRef.current = false; - - // Stop current sound immediately - if (currentPlayAllSoundRef.current) { - void currentPlayAllSoundRef.current - .stopAsync() - .then(() => { - void currentPlayAllSoundRef.current?.unloadAsync(); - currentPlayAllSoundRef.current = null; - }) - .catch(() => { - // Ignore errors during cleanup - currentPlayAllSoundRef.current = null; - }); - } - } - - // Reset state - setCurrentlyPlayingAssetId(null); + isPlayAllRunningRef.current = false; + void assetAudioRef.current.stop(); setIsPlayAllRunning(false); + setPlayAllActiveAssetId(null); }; }, []); - // OLD handlePlayAllAssets function - commented out (replaced by handlePlayAll) - // const handlePlayAllAssets = React.useCallback(async () => { - // try { - // const isPlayingAll = - // audioContext.isPlaying && - // audioContext.currentAudioId === PLAY_ALL_AUDIO_ID; - // - // if (isPlayingAll) { - // await audioContext.stopCurrentSound(); - // setCurrentlyPlayingAssetId(null); - // assetUriMapRef.current.clear(); - // assetOrderRef.current = []; - // uriOrderRef.current = []; - // segmentDurationsRef.current = []; - // assetTimeRangesRef.current = []; - // } else { - // if (assets.length === 0) { - // console.warn('⚠️ No assets to play'); - // return; - // } - // - // // Collect all URIs from all assets in order, tracking which asset each URI belongs to - // const allUris: string[] = []; - // assetUriMapRef.current.clear(); - // assetOrderRef.current = []; - // uriOrderRef.current = []; - // segmentDurationsRef.current = []; - // assetTimeRangesRef.current = []; - // - // // Build time ranges for each asset - // let cumulativeTime = 0; - // for (const asset of assets) { - // const uris = await getAssetAudioUris(asset.id); - // if (uris.length > 0) { - // const assetStartTime = cumulativeTime; - // assetOrderRef.current.push(asset.id); - // - // // Add all URIs for this asset - // for (const uri of uris) { - // allUris.push(uri); - // uriOrderRef.current.push(uri); - // assetUriMapRef.current.set(uri, asset.id); - // - // // Load duration for this URI - // try { - // const { sound } = await Audio.Sound.createAsync({ uri }); - // const status = await sound.getStatusAsync(); - // await sound.unloadAsync(); - // if (status.isLoaded) { - // const duration = status.durationMillis ?? 0; - // segmentDurationsRef.current.push(duration); - // cumulativeTime += duration; - // } else { - // segmentDurationsRef.current.push(0); - // } - // } catch { - // segmentDurationsRef.current.push(0); - // } - // } - // - // // Store the time range for this asset - // assetTimeRangesRef.current.push({ - // assetId: asset.id, - // startMs: assetStartTime, - // endMs: cumulativeTime - // }); - // - // console.log( - // `📊 Asset ${asset.id.slice(0, 8)}: ${Math.round(assetStartTime)}ms - ${Math.round(cumulativeTime)}ms (${uris.length} segments)` - // ); - // } - // } - // - // if (allUris.length === 0) { - // console.error('❌ No audio URIs found for any assets'); - // return; - // } - // - // console.log( - // `▶️ Playing ${allUris.length} audio segments from ${assets.length} assets (total: ${Math.round(cumulativeTime)}ms)` - // ); - // - // // Start playing (AudioContext will handle sequence playback) - // await audioContext.playSoundSequence(allUris, PLAY_ALL_AUDIO_ID); - // } - // } catch (error) { - // console.error('❌ Failed to play all assets:', error); - // setCurrentlyPlayingAssetId(null); - // assetUriMapRef.current.clear(); - // assetOrderRef.current = []; - // uriOrderRef.current = []; - // segmentDurationsRef.current = []; - // } - // }, [audioContext, getAssetAudioUris, assets]); - // Handle play individual asset const handlePlayAsset = React.useCallback( async (assetId: string) => { try { + // During Play All, asset tap acts as "stop play all". + if (isPlayAllRunningRef.current) { + await stopPlayAll(); + return; + } + const isThisAssetPlaying = - audioContext.isPlaying && audioContext.currentAudioId === assetId; + assetAudio.isPlaying && assetAudio.currentAudioId === assetId; if (isThisAssetPlaying) { - console.log('⏸️ Stopping asset:', assetId.slice(0, 8)); - await audioContext.stopCurrentSound(); - setCurrentlyPlayingAssetId(null); + await assetAudio.stop(); } else { - console.log('▶️ Playing asset:', assetId.slice(0, 8)); - const uris = await getAssetAudioUris(assetId); - - if (uris.length === 0) { - console.warn('⚠️ No audio URIs found for asset:', assetId); - return; - } - - // Set the asset as currently playing immediately for visual feedback - setCurrentlyPlayingAssetId(assetId); - - if (uris.length === 1 && uris[0]) { - console.log('▶️ Playing single segment'); - await audioContext.playSound(uris[0], assetId); - } else if (uris.length > 1) { - console.log(`▶️ Playing ${uris.length} segments in sequence`); - await audioContext.playSoundSequence(uris, assetId); - } + await assetAudio.play(assetId); } } catch (error) { - console.error('❌ Failed to play audio:', error); - setCurrentlyPlayingAssetId(null); + console.error('Failed to play audio:', error); } }, - [audioContext, getAssetAudioUris] + [assetAudio, stopPlayAll] ); // Update ref so renderItem can use it @@ -3399,9 +2853,13 @@ export default function BibleAssetsView() { void refetch(); console.log('✅ [Publish Quest] All queries invalidated'); + + RNAlert.alert(t('success'), result.message, [ + { text: t('ok'), isPreferred: true } + ]); } else { RNAlert.alert(t('error'), result.message || t('error'), [ - { text: t('ok') } + { text: t('ok'), isPreferred: true } ]); } }, @@ -3410,7 +2868,7 @@ export default function BibleAssetsView() { RNAlert.alert( t('error'), error instanceof Error ? error.message : t('failedCreateTranslation'), - [{ text: t('ok') }] + [{ text: t('ok'), isPreferred: true }] ); } }); @@ -3611,7 +3069,7 @@ export default function BibleAssetsView() { const projectName = currentProjectData?.name || ''; return ( - + {/* Left side: Quest name + action buttons */} @@ -3688,25 +3146,6 @@ export default function BibleAssetsView() { {/* Right side: Publish/Export buttons (isolated) */} - {/* OLD handlePlayAllAssets button - commented out (replaced by Library icon button) */} - {/* {assets.length > 0 && ( - - )} */} {isPublished ? ( // Show cloud badge and export button if user is creator, member, or owner canSeePublishedBadge ? ( @@ -3787,6 +3226,7 @@ export default function BibleAssetsView() { { text: t('publish'), style: 'default', + isPreferred: true, onPress: () => { publishQuest(); } @@ -3864,6 +3304,7 @@ export default function BibleAssetsView() { ) ) : ( item.key} renderItem={renderItem} diff --git a/views/new/BibleChapterList.tsx b/views/new/BibleChapterList.tsx index fb4f5398f..7b6928842 100644 --- a/views/new/BibleChapterList.tsx +++ b/views/new/BibleChapterList.tsx @@ -12,6 +12,7 @@ import { useProjectById } from '@/hooks/db/useProjects'; import { useAppNavigation } from '@/hooks/useAppNavigation'; import { useBibleChapterCreation } from '@/hooks/useBibleChapterCreation'; import { useBibleChapters } from '@/hooks/useBibleChapters'; +import { useColorScheme } from '@/hooks/useColorScheme'; import { useLocalization } from '@/hooks/useLocalization'; import { useQuestDownloadDiscovery } from '@/hooks/useQuestDownloadDiscovery'; import { useQuestDownloadStatusLive } from '@/hooks/useQuestDownloadStatusLive'; @@ -20,13 +21,13 @@ import { syncCallbackService } from '@/services/syncCallbackService'; import { BOOK_ICON_MAP } from '@/utils/BOOK_GRAPHICS'; import { bulkDownloadQuest } from '@/utils/bulkDownload'; import { cn, useThemeColor } from '@/utils/styleUtils'; +import RNAlert from '@blazejkustra/react-native-alert'; import { LegendList } from '@legendapp/list'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Image } from 'expo-image'; import { BookOpenIcon, HardDriveIcon } from 'lucide-react-native'; import React from 'react'; import { ActivityIndicator, TouchableOpacity, View } from 'react-native'; -import RNAlert from '@blazejkustra/react-native-alert'; interface BibleChapterListProps { projectId: string; @@ -74,6 +75,7 @@ function ChapterButton({ downloadingQuestIds?: Set; }) { const { currentUser } = useAuth(); + const { isDarkColorScheme } = useColorScheme(); const exists = !!existingChapter; const hasLocalCopy = existingChapter?.hasLocalCopy ?? false; const hasSyncedCopy = existingChapter?.hasSyncedCopy ?? false; @@ -97,19 +99,21 @@ function ChapterButton({ }; // Use semantic Tailwind colors for status, matching conventions: - // - Published: success/green (chart-3) + // - Published: primary (purple) with primary-foreground text // - Local only: info/blue (chart-2) // - Not created: muted // - Foreground should always be readable (primary-foreground for filled, foreground for outline) const getBackgroundColor = () => { - if (hasSyncedCopy) return 'bg-chart-3'; // Published (Success, Green) + if (hasSyncedCopy) return 'bg-primary'; // Published (Primary, Purple) if (hasLocalCopy) return 'bg-chart-2'; // Local-only (Info, Blue) if (exists) return 'bg-card'; // Exists but not local or synced return 'bg-muted'; // Not yet created (empty slot) }; const getTextColor = () => { - if (hasSyncedCopy || hasLocalCopy) return 'text-secondary'; + // For downloaded chapters with primary button variant, use primary-foreground (white) + if (hasSyncedCopy && exists) return 'text-primary-foreground'; + if (hasLocalCopy) return 'text-secondary'; if (exists) return 'text-foreground'; return 'text-muted-foreground'; }; @@ -121,12 +125,15 @@ function ChapterButton({ className={cn( 'w-full flex-col gap-1 py-3', !exists && 'border-dashed', - needsDownload && 'opacity-50', getBackgroundColor() )} - onPress={onPress} + onPress={ + needsDownload && !isDarkColorScheme ? handleDownloadToggle : onPress + } disabled={ - disabled || needsDownload || (!existingChapter && !canCreateNew) + disabled || + (needsDownload && isDarkColorScheme) || + (!existingChapter && !canCreateNew) } > {isCreatingThis ? ( @@ -145,9 +152,11 @@ function ChapterButton({ onPress={handleDownloadToggle} size={16} iconColor={ - hasSyncedCopy || hasLocalCopy - ? 'text-secondary' - : 'text-foreground' + hasSyncedCopy && exists + ? 'text-primary-foreground' + : hasLocalCopy + ? 'text-secondary' + : 'text-foreground' } /> @@ -168,8 +177,8 @@ function ChapterButton({ )} - {/* Overlay to make entire button pressable for download when needed */} - {needsDownload && !disabled && ( + {/* Overlay to make entire button pressable for download when needed (dark theme only) */} + {needsDownload && !disabled && isDarkColorScheme && ( { void (async () => { setCreatingChapter(chapterNum); diff --git a/views/new/NextGenAssetsView.tsx b/views/new/NextGenAssetsView.tsx index 485f63742..74585a767 100644 --- a/views/new/NextGenAssetsView.tsx +++ b/views/new/NextGenAssetsView.tsx @@ -29,7 +29,7 @@ import { useLocalStore } from '@/store/localStore'; import { SHOW_DEV_ELEMENTS } from '@/utils/featureFlags'; import RNAlert from '@blazejkustra/react-native-alert'; import { LegendList } from '@legendapp/list'; -import { Audio } from 'expo-av'; +import { createAudioPlayer, type AudioPlayer } from 'expo-audio'; import { ArrowBigDownDashIcon, CheckCheck, @@ -133,7 +133,7 @@ export default function NextGenAssetsView() { // New PlayAll state (starts from selected asset) const [isPlayAllRunning, setIsPlayAllRunning] = React.useState(false); const isPlayAllRunningRef = React.useRef(false); - const currentPlayAllSoundRef = React.useRef(null); + const currentPlayAllSoundRef = React.useRef(null); const timeoutIdsRef = React.useRef>>( new Set() ); @@ -833,8 +833,8 @@ export default function NextGenAssetsView() { // Stop current sound immediately if (currentPlayAllSoundRef.current) { try { - await currentPlayAllSoundRef.current.stopAsync(); - await currentPlayAllSoundRef.current.unloadAsync(); + currentPlayAllSoundRef.current.pause(); + currentPlayAllSoundRef.current.release(); currentPlayAllSoundRef.current = null; } catch (error) { console.error('Error stopping sound:', error); @@ -938,29 +938,22 @@ export default function NextGenAssetsView() { // Play this URI and wait for it to finish await new Promise((resolve) => { - // Create and play the sound - Audio.Sound.createAsync({ uri }, { shouldPlay: true }) - .then(({ sound }) => { - // Store reference for immediate cancellation - currentPlayAllSoundRef.current = sound; - - // Set up listener for when sound finishes - sound.setOnPlaybackStatusUpdate((status) => { - if (!status.isLoaded) return; - - if (status.didJustFinish) { - currentPlayAllSoundRef.current = null; - void sound.unloadAsync().then(() => { - resolve(); - }); - } - }); - }) - .catch((error) => { - console.error('Failed to play audio:', error); + try { + const player = createAudioPlayer(uri); + currentPlayAllSoundRef.current = player; + player.play(); + + player.addListener('playbackStatusUpdate', (status) => { + if (!status.didJustFinish) return; currentPlayAllSoundRef.current = null; - resolve(); // Continue to next even on error + player.release(); + resolve(); }); + } catch (error) { + console.error('Failed to play audio:', error); + currentPlayAllSoundRef.current = null; + resolve(); // Continue to next even on error + } }); } } @@ -1035,9 +1028,13 @@ export default function NextGenAssetsView() { void refetch(); console.log('✅ [Publish Quest] All queries invalidated'); + + RNAlert.alert(t('success'), result.message, [ + { text: t('ok'), isPreferred: true } + ]); } else { RNAlert.alert(t('error'), result.message || t('error'), [ - { text: t('ok') } + { text: t('ok'), isPreferred: true } ]); } }, @@ -1046,7 +1043,7 @@ export default function NextGenAssetsView() { RNAlert.alert( t('error'), error instanceof Error ? error.message : t('failedCreateTranslation'), - [{ text: t('ok') }] + [{ text: t('ok'), isPreferred: true }] ); } }); @@ -1152,8 +1149,8 @@ export default function NextGenAssetsView() { // Stop current sound immediately if (currentPlayAllSoundRef.current) { try { - await currentPlayAllSoundRef.current.stopAsync(); - await currentPlayAllSoundRef.current.unloadAsync(); + currentPlayAllSoundRef.current.pause(); + currentPlayAllSoundRef.current.release(); currentPlayAllSoundRef.current = null; } catch (error) { console.error('Error stopping sound:', error); @@ -1191,16 +1188,13 @@ export default function NextGenAssetsView() { // Stop current sound immediately if (currentPlayAllSoundRef.current) { - void currentPlayAllSoundRef.current - .stopAsync() - .then(() => { - void currentPlayAllSoundRef.current?.unloadAsync(); - currentPlayAllSoundRef.current = null; - }) - .catch(() => { - // Ignore errors during cleanup - currentPlayAllSoundRef.current = null; - }); + try { + currentPlayAllSoundRef.current.pause(); + currentPlayAllSoundRef.current.release(); + } catch { + // Ignore errors during cleanup + } + currentPlayAllSoundRef.current = null; } } @@ -1243,7 +1237,7 @@ export default function NextGenAssetsView() { const projectName = currentProjectData?.name || ''; return ( - + {t('assets')} @@ -1369,6 +1363,7 @@ export default function NextGenAssetsView() { { text: t('publish'), style: 'default', + isPreferred: true, onPress: () => { publishQuest(); } diff --git a/views/new/NextGenNewTranslationModal.tsx b/views/new/NextGenNewTranslationModal.tsx index 86adcec0f..714f6d889 100644 --- a/views/new/NextGenNewTranslationModal.tsx +++ b/views/new/NextGenNewTranslationModal.tsx @@ -585,7 +585,7 @@ export default function NextGenNewTranslationModal({ RNAlert.alert( 'No Source Text', 'There is no source text to translate. Please select an asset with content.', - [{ text: 'OK' }] + [{ text: 'OK', isPreferred: true }] ); return; } @@ -594,7 +594,7 @@ export default function NextGenNewTranslationModal({ RNAlert.alert( 'Offline', 'AI translation requires an internet connection. Please check your network and try again.', - [{ text: 'OK' }] + [{ text: 'OK', isPreferred: true }] ); return; } @@ -603,7 +603,7 @@ export default function NextGenNewTranslationModal({ RNAlert.alert( 'Missing Language Info', 'Target language information is not available. Please select a target language.', - [{ text: 'OK' }] + [{ text: 'OK', isPreferred: true }] ); return; } diff --git a/views/new/NextGenProjectsView.tsx b/views/new/NextGenProjectsView.tsx index 8cc68257a..09c68241e 100644 --- a/views/new/NextGenProjectsView.tsx +++ b/views/new/NextGenProjectsView.tsx @@ -743,7 +743,7 @@ export default function NextGenProjectsView() { snapPoints={[700]} enableDynamicSizing={false} > - + {/* Tabs */} )} - + diff --git a/views/new/ProjectDirectoryView.tsx b/views/new/ProjectDirectoryView.tsx index a6d4e3502..56742596a 100644 --- a/views/new/ProjectDirectoryView.tsx +++ b/views/new/ProjectDirectoryView.tsx @@ -946,7 +946,7 @@ export default function ProjectDirectoryView() { // Default unstructured project view return ( - + diff --git a/views/new/QuestTreeRow.tsx b/views/new/QuestTreeRow.tsx index b1b0ca389..f305b779e 100644 --- a/views/new/QuestTreeRow.tsx +++ b/views/new/QuestTreeRow.tsx @@ -73,6 +73,7 @@ export const QuestTreeRow: React.FC = ({ { text: t('cancel'), style: 'cancel' }, { text: t('downloadNow'), + isPreferred: true, onPress: () => { if (onDownloadClick) { onDownloadClick(quest.id); diff --git a/views/new/RecordingView.tsx b/views/new/RecordingView.tsx index 585575ca2..df55049d8 100644 --- a/views/new/RecordingView.tsx +++ b/views/new/RecordingView.tsx @@ -14,7 +14,11 @@ import { updateContentLinkOrder } from '@/database_services/assetService'; import { audioSegmentService } from '@/database_services/audioSegmentService'; -import { asset_content_link, project_language_link } from '@/db/drizzleSchema'; +import { + asset_content_link, + project as projectTable, + project_language_link +} from '@/db/drizzleSchema'; import { system } from '@/db/powersync/system'; import type { Project } from '@/hooks/db/useProjects'; import { @@ -23,9 +27,9 @@ import { } from '@/hooks/useAppNavigation'; import { useLocalization } from '@/hooks/useLocalization'; import { useLocalStore } from '@/store/localStore'; +import { useAssetAudio } from '@/services/assetAudio'; import { resolveTable } from '@/utils/dbUtils'; import { - fileExists, getLocalAttachmentUriWithOPFS, saveAudioLocally } from '@/utils/fileUtils'; @@ -34,7 +38,7 @@ import { toCompilableQuery } from '@powersync/drizzle-driver'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { and, asc, eq } from 'drizzle-orm'; -import { Audio } from 'expo-av'; +import { createAudioPlayer } from 'expo-audio'; import { ArrowLeft, ChevronLeft, @@ -255,7 +259,7 @@ const RecordingView = () => { queryFn: async () => { if (!currentProjectId) return null; const result = await system.db.query.project.findFirst({ - where: (fields, { eq }) => eq(fields.id, currentProjectId) + where: eq(projectTable.id, currentProjectId) }); return (result as Project) ?? null; }, @@ -264,6 +268,7 @@ const RecordingView = () => { }); const audioContext = useAudio(); + const assetAudio = useAssetAudio(); const insets = useSafeAreaInsets(); // NEEDS TO GO TO THE CLOUD WHEN PROJECT IS NOT CREATED IN THE SAME DEVICE @@ -411,9 +416,6 @@ const RecordingView = () => { const [isPlayAllRunning, setIsPlayAllRunning] = React.useState(false); // Ref to track if handlePlayAll is running (for cancellation) const isPlayAllRunningRef = React.useRef(false); - // Ref to track current playing sound for immediate cancellation - const currentPlayAllSoundRef = React.useRef(null); - // Track setTimeout IDs for cleanup const timeoutIdsRef = React.useRef>>( new Set() @@ -492,8 +494,8 @@ const RecordingView = () => { // When user exits and returns, the list starts with just the initial verse pill // Assets are still saved to database, but we don't load existing ones const [sessionItems, setSessionItems] = React.useState(() => { - // Initialize with the initial verse pill if (!_verse) return []; + const initialVerse = _verse; const initialPill: VersePillItem = { type: 'pill', @@ -983,344 +985,97 @@ const RecordingView = () => { // AUDIO PLAYBACK // ============================================================================ - // Fetch audio URIs for an asset - // Includes fallback logic for local-only files when server records are removed - const getAssetAudioUris = React.useCallback( - async (assetId: string): Promise => { - try { - // Get content links from both synced and local tables - const assetContentLinkSynced = resolveTable('asset_content_link', { - localOverride: false - }); - const contentLinksSynced = await system.db - .select() - .from(assetContentLinkSynced) - .where(eq(assetContentLinkSynced.asset_id, assetId)); - - const assetContentLinkLocal = resolveTable('asset_content_link', { - localOverride: true - }); - const contentLinksLocal = await system.db - .select() - .from(assetContentLinkLocal) - .where(eq(assetContentLinkLocal.asset_id, assetId)); - - // Prefer synced links, but merge with local for fallback - const allContentLinks = [...contentLinksSynced, ...contentLinksLocal]; - - // Deduplicate by ID (prefer synced over local) - const seenIds = new Set(); - const uniqueLinks = allContentLinks.filter((link) => { - if (seenIds.has(link.id)) { - return false; - } - seenIds.add(link.id); - return true; - }); - - debugLog( - `📀 Found ${uniqueLinks.length} content link(s) for asset ${assetId.slice(0, 8)} (${contentLinksSynced.length} synced, ${contentLinksLocal.length} local)` - ); - - if (uniqueLinks.length === 0) { - debugLog('No content links found for asset:', assetId); - return []; - } - - // Get audio values from content links (can be URIs or attachment IDs) - const audioValues = uniqueLinks - .flatMap((link) => { - const audioArray = link.audio ?? []; - debugLog( - ` 📎 Content link has ${audioArray.length} audio file(s):`, - audioArray - ); - return audioArray; - }) - .filter((value): value is string => !!value); - - debugLog(`📊 Total audio files for asset: ${audioValues.length}`); + const stopPlayAll = React.useCallback(async () => { + isPlayAllRunningRef.current = false; + setIsPlayAllRunning(false); + setCurrentlyPlayingAssetId(null); + await assetAudio.stop(); + }, [assetAudio]); - if (audioValues.length === 0) { - debugLog('No audio values found in content links'); - return []; - } + const activateAssetForPlayAll = React.useCallback( + async (assetId: string, wheelIndex: number) => { + setCurrentlyPlayingAssetId(assetId); + await Promise.resolve(); - // Process each audio value - can be either a local URI or an attachment ID - const uris: string[] = []; - for (const audioValue of audioValues) { - // Check if this is already a local URI (starts with 'local/' or 'file://') - if (audioValue.startsWith('local/')) { - // It's a direct local URI from saveAudioLocally() - const constructedUri = - await getLocalAttachmentUriWithOPFS(audioValue); - // Check if file exists at constructed path - if (await fileExists(constructedUri)) { - uris.push(constructedUri); - debugLog( - '✅ Using direct local URI:', - constructedUri.slice(0, 80) - ); - } else { - // File doesn't exist at expected path - try to find it in attachment queue - debugLog( - `⚠️ Local URI ${audioValue} not found at ${constructedUri}, searching attachment queue...` - ); + if (listRef.current) { + listRef.current.scrollToIndex(wheelIndex - 1, true); + } + }, + [] + ); - if (system.permAttachmentQueue) { - // Extract filename from local path (e.g., "local/uuid.wav" -> "uuid.wav") - const filename = audioValue.replace(/^local\//, ''); - // Extract UUID part (without extension) for more flexible matching - const uuidPart = filename.split('.')[0]; - - // Search attachment queue by filename or UUID - let attachment = await system.powersync.getOptional<{ - id: string; - filename: string | null; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE filename = ? OR filename LIKE ? OR id = ? OR id LIKE ? LIMIT 1`, - [filename, `%${uuidPart}%`, filename, `%${uuidPart}%`] - ); + const runPlayAll = React.useCallback( + async (startIndex: number) => { + const isPlayAllActive = () => isPlayAllRunningRef.current; + if (startIndex >= sessionItems.length) { + console.warn('⚠️ No items to play from current position'); + return; + } - // If not found, try searching all attachments for this asset's content links - if (!attachment && uniqueLinks.length > 0) { - const allAttachmentIds = uniqueLinks - .flatMap((link) => link.audio ?? []) - .filter( - (av): av is string => - typeof av === 'string' && - !av.startsWith('local/') && - !av.startsWith('file://') - ); - if (allAttachmentIds.length > 0) { - const placeholders = allAttachmentIds - .map(() => '?') - .join(','); - attachment = await system.powersync.getOptional<{ - id: string; - filename: string | null; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE id IN (${placeholders}) LIMIT 1`, - allAttachmentIds - ); - } - } + isPlayAllRunningRef.current = true; + setIsPlayAllRunning(true); - if (attachment?.local_uri) { - const foundUri = system.permAttachmentQueue.getLocalUri( - attachment.local_uri - ); - // Verify the found file actually exists - if (await fileExists(foundUri)) { - uris.push(foundUri); - debugLog( - `✅ Found attachment in queue for local URI ${audioValue.slice(0, 20)}` - ); - } else { - debugLog( - `⚠️ Attachment found in queue but file doesn't exist: ${foundUri}` - ); - } - } else { - // Try fallback to local table for alternative audio values - const fallbackLink = contentLinksLocal.find( - (link) => link.asset_id === assetId - ); - if (fallbackLink?.audio) { - for (const fallbackAudioValue of fallbackLink.audio) { - if (fallbackAudioValue.startsWith('file://')) { - if (await fileExists(fallbackAudioValue)) { - uris.push(fallbackAudioValue); - debugLog(`✅ Found fallback file URI`); - break; - } - } - } - } - } - } - } - } else if (audioValue.startsWith('file://')) { - // Already a full file URI - verify it exists - if (await fileExists(audioValue)) { - uris.push(audioValue); - debugLog('✅ Using full file URI:', audioValue.slice(0, 80)); - } else { - debugLog(`⚠️ File URI does not exist: ${audioValue}`); - // Try to find in attachment queue by extracting filename from path - if (system.permAttachmentQueue) { - const filename = audioValue.split('/').pop(); - if (filename) { - const attachment = await system.powersync.getOptional<{ - id: string; - filename: string | null; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE filename = ? OR id = ? LIMIT 1`, - [filename, filename] - ); + try { + let wheelIndex = startIndex; + while (wheelIndex < sessionItems.length) { + if (!isPlayAllActive()) break; - if (attachment?.local_uri) { - const foundUri = system.permAttachmentQueue.getLocalUri( - attachment.local_uri - ); - if (await fileExists(foundUri)) { - uris.push(foundUri); - debugLog(`✅ Found attachment in queue for file URI`); - } - } - } - } - } - } else { - // It's an attachment ID - look it up in the attachment queue - if (!system.permAttachmentQueue) { - // No attachment queue - try fallback to local table - const fallbackLink = contentLinksLocal.find( - (link) => link.asset_id === assetId - ); - if (fallbackLink?.audio) { - for (const fallbackAudioValue of fallbackLink.audio) { - if (fallbackAudioValue.startsWith('local/')) { - const fallbackUri = - await getLocalAttachmentUriWithOPFS(fallbackAudioValue); - if (await fileExists(fallbackUri)) { - uris.push(fallbackUri); - break; - } - } else if (fallbackAudioValue.startsWith('file://')) { - if (await fileExists(fallbackAudioValue)) { - uris.push(fallbackAudioValue); - break; - } - } - } - } - continue; - } + const item = sessionItems[wheelIndex]; + if (!item || isPill(item)) { + wheelIndex += 1; + continue; + } - const attachment = await system.powersync.getOptional<{ - id: string; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE id = ?`, - [audioValue] - ); + const assetId = item.id; + await activateAssetForPlayAll(assetId, wheelIndex); + if (!isPlayAllActive()) break; - if (attachment?.local_uri) { - const localUri = system.permAttachmentQueue.getLocalUri( - attachment.local_uri - ); - if (await fileExists(localUri)) { - uris.push(localUri); - debugLog('✅ Found attachment URI:', localUri.slice(0, 60)); - } - } else { - // Attachment ID not found in queue - try fallback to local table - debugLog( - `⚠️ Attachment ID ${audioValue.slice(0, 8)} not found in queue, checking local table fallback...` - ); + await assetAudio.play(assetId); + await assetAudio.waitForPlaybackEnd(assetId); + if (!isPlayAllActive()) break; - const fallbackLink = contentLinksLocal.find( - (link) => link.asset_id === assetId - ); - if (fallbackLink?.audio) { - for (const fallbackAudioValue of fallbackLink.audio) { - if (fallbackAudioValue.startsWith('local/')) { - const fallbackUri = - await getLocalAttachmentUriWithOPFS(fallbackAudioValue); - if (await fileExists(fallbackUri)) { - uris.push(fallbackUri); - debugLog( - `✅ Found fallback local URI for attachment ${audioValue.slice(0, 8)}` - ); - break; - } - } else if (fallbackAudioValue.startsWith('file://')) { - if (await fileExists(fallbackAudioValue)) { - uris.push(fallbackAudioValue); - debugLog( - `✅ Found fallback file URI for attachment ${audioValue.slice(0, 8)}` - ); - break; - } - } - } - } else { - debugLog(`⚠️ Audio ${audioValue} not downloaded yet`); - } - } - } + wheelIndex += 1; } - - return uris; - } catch (error) { - console.error('Failed to fetch audio URIs:', error); - return []; + } finally { + isPlayAllRunningRef.current = false; + setIsPlayAllRunning(false); + setCurrentlyPlayingAssetId(null); } }, - [] + [sessionItems, activateAssetForPlayAll, assetAudio] ); // Handle asset playback const handlePlayAsset = React.useCallback( async (assetId: string) => { try { + if (isPlayAllRunningRef.current) { + await stopPlayAll(); + return; + } + const isThisAssetPlaying = - audioContext.isPlaying && audioContext.currentAudioId === assetId; + assetAudio.isPlaying && assetAudio.currentAudioId === assetId; if (isThisAssetPlaying) { debugLog('⏸️ Stopping asset:', assetId.slice(0, 8)); - await audioContext.stopCurrentSound(); + await assetAudio.stop(); } else { debugLog('▶️ Playing asset:', assetId.slice(0, 8)); - const uris = await getAssetAudioUris(assetId); - - if (uris.length === 0) { - console.error('❌ No audio URIs found for asset:', assetId); - return; - } - - if (uris.length === 1 && uris[0]) { - debugLog('▶️ Playing single segment'); - await audioContext.playSound(uris[0], assetId); - } else if (uris.length > 1) { - debugLog(`▶️ Playing ${uris.length} segments in sequence`); - await audioContext.playSoundSequence(uris, assetId); - } + await assetAudio.play(assetId); } } catch (error) { console.error('❌ Failed to play audio:', error); } }, - [audioContext, getAssetAudioUris] + [assetAudio, stopPlayAll] ); - // Handle play all assets - optimized version with direct control + // Handle play all assets - event-driven via AssetAudio const handlePlayAll = React.useCallback(async () => { try { - // Check if already playing - toggle to stop if (isPlayAllRunningRef.current) { - isPlayAllRunningRef.current = false; - setIsPlayAllRunning(false); - - // Stop current sound immediately - if (currentPlayAllSoundRef.current) { - try { - await currentPlayAllSoundRef.current.stopAsync(); - await currentPlayAllSoundRef.current.unloadAsync(); - currentPlayAllSoundRef.current = null; - } catch (error) { - console.error('Error stopping sound:', error); - } - } - - setCurrentlyPlayingAssetId(null); - // Reset SharedValues when stopping - audioContext.positionShared.value = 0; - audioContext.durationShared.value = 0; + await stopPlayAll(); debugLog('⏸️ Stopped play all'); return; } @@ -1330,143 +1085,18 @@ const RecordingView = () => { return; } - // Mark as running - isPlayAllRunningRef.current = true; - setIsPlayAllRunning(true); - // If no item is selected (at end of list), start from the beginning // Otherwise, start from the selected item const startIndex = insertionIndex >= sessionItems.length ? 0 : insertionIndex; - debugLog( - `🎵 Starting play all from position ${startIndex} (insertionIndex: ${insertionIndex})` - ); - - let assetsPlayed = 0; - - // Iterate directly through sessionItems starting from startIndex - for ( - let wheelIndex = startIndex; - wheelIndex < sessionItems.length; - wheelIndex++ - ) { - // Check if cancelled - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!isPlayAllRunningRef.current) { - debugLog('⏸️ Play all cancelled'); - setCurrentlyPlayingAssetId(null); - // Reset SharedValues when cancelled - audioContext.positionShared.value = 0; - audioContext.durationShared.value = 0; - return; - } - - const item = sessionItems[wheelIndex]; - - // Skip if no item or if it's a pill - if (!item || isPill(item)) { - debugLog(`⏭️ Position ${wheelIndex}: skipping pill`); - continue; - } - - // It's an asset - play it - const asset = item; - - // Get URIs for this asset - const uris = await getAssetAudioUris(asset.id); - if (uris.length === 0) { - debugLog( - `⚠️ Position ${wheelIndex}: no URIs for asset ${asset.name}` - ); - continue; - } - - // HIGHLIGHT THIS ASSET - setCurrentlyPlayingAssetId(asset.id); - - // Scroll to this position in the list - if (listRef.current) { - listRef.current.scrollToIndex(wheelIndex - 1, true); - } - - assetsPlayed++; - debugLog( - `▶️ Position ${wheelIndex}: Playing asset ${asset.name} (${uris.length} segments)` - ); - - // Play all URIs for this asset sequentially - for (const uri of uris) { - // Check if cancelled - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!isPlayAllRunningRef.current) { - setCurrentlyPlayingAssetId(null); - // Reset SharedValues when cancelled - audioContext.positionShared.value = 0; - audioContext.durationShared.value = 0; - return; - } - - // Play this URI and wait for it to finish - await new Promise((resolve) => { - Audio.Sound.createAsync({ uri }, { shouldPlay: true }) - .then(({ sound }) => { - currentPlayAllSoundRef.current = sound; - - sound.setOnPlaybackStatusUpdate((status) => { - if (!status.isLoaded) return; - - // Update SharedValues for progress bar animation (60fps on UI thread) - if (status.isPlaying) { - audioContext.positionShared.value = status.positionMillis; - audioContext.durationShared.value = - status.durationMillis ?? 0; - } - - if (status.didJustFinish) { - // Reset SharedValues when segment finishes - audioContext.positionShared.value = 0; - audioContext.durationShared.value = 0; - currentPlayAllSoundRef.current = null; - void sound.unloadAsync().then(() => { - resolve(); - }); - } - }); - }) - .catch((error) => { - console.error('Failed to play audio:', error); - currentPlayAllSoundRef.current = null; - resolve(); - }); - }); - } - } - - if (assetsPlayed === 0) { - console.warn('⚠️ No assets found to play from current position'); - } - - // Done playing all + await runPlayAll(startIndex); debugLog('✅ Finished playing all assets'); - setCurrentlyPlayingAssetId(null); - // Reset SharedValues when finished - audioContext.positionShared.value = 0; - audioContext.durationShared.value = 0; - isPlayAllRunningRef.current = false; - setIsPlayAllRunning(false); - currentPlayAllSoundRef.current = null; } catch (error) { console.error('❌ Failed to play all assets:', error); - setCurrentlyPlayingAssetId(null); - // Reset SharedValues on error - audioContext.positionShared.value = 0; - audioContext.durationShared.value = 0; - isPlayAllRunningRef.current = false; - setIsPlayAllRunning(false); - currentPlayAllSoundRef.current = null; + await stopPlayAll(); } - }, [audioContext, getAssetAudioUris, insertionIndex, sessionItems]); + }, [insertionIndex, runPlayAll, sessionItems.length, stopPlayAll]); // ============================================================================ // RECORDING HANDLERS @@ -2138,8 +1768,10 @@ const RecordingView = () => { // AUDIO FILES: Extract all audio file references from all segments // This flattens the audio arrays from all content_link rows const audioValues = contentLinks - .flatMap((link) => link.audio ?? []) - .filter((value): value is string => !!value); + .flatMap((link: { audio: string[] | null }) => link.audio ?? []) + .filter( + (value: string | null | undefined): value is string => !!value + ); // DEBUG: Log audio values found debugLog( @@ -2178,15 +1810,30 @@ const RecordingView = () => { if (audioUri) { // Load audio file to get duration - const { sound } = await Audio.Sound.createAsync({ - uri: audioUri + const player = createAudioPlayer(audioUri); + // Wait for player to load + await new Promise((resolve) => { + if (player.isLoaded) { + resolve(); + return; + } + const check = setInterval(() => { + if (player.isLoaded) { + clearInterval(check); + resolve(); + } + }, 10); + // Safety timeout + setTimeout(() => { + clearInterval(check); + resolve(); + }, 5000); }); - const status = await sound.getStatusAsync(); - await sound.unloadAsync(); - if (status.isLoaded && status.durationMillis) { - totalDuration += status.durationMillis; + if (player.isLoaded && player.duration) { + totalDuration += player.duration * 1000; } + player.release(); } } catch (err) { // Skip this segment if we can't load it @@ -2366,7 +2013,7 @@ const RecordingView = () => { .orderBy(asc(contentLocal.order_index), asc(contentLocal.created_at)); await updateContentLinkOrder( first.id, - allContent.map((c) => c.id), + allContent.map((c: { id: string }) => c.id), { localOverride: true } ); @@ -2471,7 +2118,7 @@ const RecordingView = () => { ); await updateContentLinkOrder( target.id, - allContent.map((c) => c.id), + allContent.map((c: { id: string }) => c.id), { localOverride: true } ); @@ -2673,20 +2320,6 @@ const RecordingView = () => { // Stop PlayAll if running if (isPlayAllRunningRef.current) { isPlayAllRunningRef.current = false; - - // Stop current sound immediately - if (currentPlayAllSoundRef.current) { - void currentPlayAllSoundRef.current - .stopAsync() - .then(() => { - void currentPlayAllSoundRef.current?.unloadAsync(); - currentPlayAllSoundRef.current = null; - }) - .catch(() => { - // Ignore errors during cleanup - currentPlayAllSoundRef.current = null; - }); - } } // Clear all refs to free memory diff --git a/views/new/recording/components/BibleRecordingView.tsx b/views/new/recording/components/BibleRecordingView.tsx index 500eae788..329690274 100644 --- a/views/new/recording/components/BibleRecordingView.tsx +++ b/views/new/recording/components/BibleRecordingView.tsx @@ -29,7 +29,7 @@ import { toCompilableQuery } from '@powersync/drizzle-driver'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useQueryClient } from '@tanstack/react-query'; import { and, asc, eq } from 'drizzle-orm'; -import { Audio } from 'expo-av'; +import { createAudioPlayer, type AudioPlayer } from 'expo-audio'; import { ArrowDownNarrowWide, ArrowLeft, @@ -298,7 +298,7 @@ const BibleRecordingView = ({ // Ref to track if handlePlayAll is running (for cancellation) const isPlayAllRunningRef = React.useRef(false); // Ref to track current playing sound for immediate cancellation - const currentPlayAllSoundRef = React.useRef(null); + const currentPlayAllSoundRef = React.useRef(null); // Track setTimeout IDs for cleanup const timeoutIdsRef = React.useRef>>( @@ -1175,8 +1175,8 @@ const BibleRecordingView = ({ // Stop current sound immediately if (currentPlayAllSoundRef.current) { try { - await currentPlayAllSoundRef.current.stopAsync(); - await currentPlayAllSoundRef.current.unloadAsync(); + currentPlayAllSoundRef.current.pause(); + currentPlayAllSoundRef.current.release(); currentPlayAllSoundRef.current = null; } catch (error) { console.error('Error stopping sound:', error); @@ -1259,26 +1259,22 @@ const BibleRecordingView = ({ // Play this URI and wait for it to finish await new Promise((resolve) => { - Audio.Sound.createAsync({ uri }, { shouldPlay: true }) - .then(({ sound }) => { - currentPlayAllSoundRef.current = sound; - - sound.setOnPlaybackStatusUpdate((status) => { - if (!status.isLoaded) return; + try { + const player = createAudioPlayer(uri); + currentPlayAllSoundRef.current = player; + player.play(); - if (status.didJustFinish) { - currentPlayAllSoundRef.current = null; - void sound.unloadAsync().then(() => { - resolve(); - }); - } - }); - }) - .catch((error) => { - console.error('Failed to play audio:', error); + player.addListener('playbackStatusUpdate', (status) => { + if (!status.didJustFinish) return; currentPlayAllSoundRef.current = null; + player.release(); resolve(); }); + } catch (error) { + console.error('Failed to play audio:', error); + currentPlayAllSoundRef.current = null; + resolve(); + } }); } } @@ -1902,15 +1898,28 @@ const BibleRecordingView = ({ if (audioUri) { // Load audio file to get duration - const { sound } = await Audio.Sound.createAsync({ - uri: audioUri + const player = createAudioPlayer(audioUri); + await new Promise((resolve) => { + if (player.isLoaded) { + resolve(); + return; + } + const check = setInterval(() => { + if (player.isLoaded) { + clearInterval(check); + resolve(); + } + }, 10); + setTimeout(() => { + clearInterval(check); + resolve(); + }, 5000); }); - const status = await sound.getStatusAsync(); - await sound.unloadAsync(); - if (status.isLoaded && status.durationMillis) { - totalDuration += status.durationMillis; + if (player.isLoaded && player.duration > 0) { + totalDuration += player.duration * 1000; } + player.release(); } } catch (err) { // Skip this segment if we can't load it @@ -2127,6 +2136,7 @@ const BibleRecordingView = ({ { text: 'Merge', style: 'destructive', + isPreferred: true, onPress: () => { void (async () => { try { @@ -2235,6 +2245,7 @@ const BibleRecordingView = ({ { text: 'Delete', style: 'destructive', + isPreferred: true, onPress: () => { void (async () => { try { @@ -2369,16 +2380,13 @@ const BibleRecordingView = ({ // Stop current sound immediately if (currentPlayAllSoundRef.current) { - void currentPlayAllSoundRef.current - .stopAsync() - .then(() => { - void currentPlayAllSoundRef.current?.unloadAsync(); - currentPlayAllSoundRef.current = null; - }) - .catch(() => { - // Ignore errors during cleanup - currentPlayAllSoundRef.current = null; - }); + try { + currentPlayAllSoundRef.current.pause(); + currentPlayAllSoundRef.current.release(); + } catch { + // Ignore errors during cleanup + } + currentPlayAllSoundRef.current = null; } } diff --git a/views/new/recording/components/RecordAssetCard.tsx b/views/new/recording/components/RecordAssetCard.tsx index 0ecc82a73..3bc1aa138 100644 --- a/views/new/recording/components/RecordAssetCard.tsx +++ b/views/new/recording/components/RecordAssetCard.tsx @@ -201,14 +201,17 @@ function RecordAssetCardInternal({ const progressBarStyle = useAnimatedStyle(() => { 'worklet'; const progress = animatedProgress.value; - const width = interpolate( + const widthPercent = interpolate( progress, [0, 95, 100], [0, 97, 100], Extrapolation.CLAMP ); + const scaleX = widthPercent / 100; return { - width: `${width}%` + width: '100%', + transform: [{ scaleX }], + transformOrigin: 'left center' }; }); diff --git a/views/new/recording/components/RecordingControls.tsx b/views/new/recording/components/RecordingControls.tsx index ca6745799..8648e95cf 100644 --- a/views/new/recording/components/RecordingControls.tsx +++ b/views/new/recording/components/RecordingControls.tsx @@ -17,7 +17,10 @@ import { TooltipTrigger } from '@/components/ui/tooltip'; import { useLocalization } from '@/hooks/useLocalization'; -import { Audio } from 'expo-av'; +import { + getRecordingPermissionsAsync, + requestRecordingPermissionsAsync +} from 'expo-audio'; import { CircleHelp, MicOffIcon, @@ -100,7 +103,7 @@ export const RecordingControls = React.memo( const checkPermission = async () => { try { - const permission = await Audio.getPermissionsAsync(); + const permission = await getRecordingPermissionsAsync(); if (!cancelled) { const wasGranted = permission.granted; setHasPermission(wasGranted); @@ -125,7 +128,7 @@ export const RecordingControls = React.memo( // Request permission handler const handleRequestPermission = async () => { try { - const permission = await Audio.requestPermissionsAsync(); + const permission = await requestRecordingPermissionsAsync(); const wasGranted = permission.granted; const wasPreviouslyDenied = previousPermissionRef.current === false; diff --git a/views/new/recording/components/RecordingViewSimplified.tsx b/views/new/recording/components/RecordingViewSimplified.tsx index ed5c2afbf..04dc7a2e1 100644 --- a/views/new/recording/components/RecordingViewSimplified.tsx +++ b/views/new/recording/components/RecordingViewSimplified.tsx @@ -4,7 +4,6 @@ import { RecordingHelpDialog } from '@/components/RecordingHelpDialog'; import { Button } from '@/components/ui/button'; import { Icon } from '@/components/ui/icon'; import { Text } from '@/components/ui/text'; -import { useAudio } from '@/contexts/AudioContext'; import { useAuth } from '@/contexts/AuthContext'; import { getNextOrderIndex as getNextAclOrderIndex, @@ -21,10 +20,10 @@ import { system } from '@/db/powersync/system'; import { useProjectById } from '@/hooks/db/useProjects'; import { useCurrentNavigation } from '@/hooks/useAppNavigation'; import { useLocalization } from '@/hooks/useLocalization'; +import { useAssetAudio } from '@/services/assetAudio'; import { useLocalStore } from '@/store/localStore'; import { resolveTable } from '@/utils/dbUtils'; import { - fileExists, getLocalAttachmentUriWithOPFS, saveAudioLocally } from '@/utils/fileUtils'; @@ -34,11 +33,10 @@ import { LegendList } from '@legendapp/list'; import { toCompilableQuery } from '@powersync/drizzle-driver'; import { useQueryClient } from '@tanstack/react-query'; import { and, asc, eq, getTableColumns } from 'drizzle-orm'; -import { Audio } from 'expo-av'; +import { createAudioPlayer } from 'expo-audio'; import { ArrowLeft, ListVideo, PauseIcon } from 'lucide-react-native'; import React from 'react'; import { InteractionManager, View } from 'react-native'; -import { useSharedValue } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useHybridData } from '../../useHybridData'; import { useSelectionMode } from '../hooks/useSelectionMode'; @@ -53,7 +51,7 @@ import { VADSettingsDrawer } from './VADSettingsDrawer'; // Feature flag: true = use ArrayInsertionWheel, false = use LegendList const USE_INSERTION_WHEEL = true; -const DEBUG_MODE = false; +const DEBUG_MODE = true; function debugLog(...args: unknown[]) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (DEBUG_MODE) { @@ -87,7 +85,7 @@ const RecordingViewSimplified = ({ const { currentQuestId, currentProjectId } = navigation; const { currentUser } = useAuth(); const { project: currentProject } = useProjectById(currentProjectId); - const audioContext = useAudio(); + const audio = useAssetAudio(); const insets = useSafeAreaInsets(); // Get target languoid_id from project_language_link @@ -165,25 +163,18 @@ const RecordingViewSimplified = ({ const [currentlyPlayingAssetId, setCurrentlyPlayingAssetId] = React.useState< string | null >(null); - const assetUriMapRef = React.useRef>(new Map()); // URI -> assetId - const segmentDurationsRef = React.useRef([]); // Duration of each URI segment in ms - // Track segment ranges for each asset (start position, end position, duration) - const assetSegmentRangesRef = React.useRef< - Map - >(new Map()); // Track last scrolled asset to avoid scrolling to the same asset multiple times const lastScrolledAssetIdRef = React.useRef(null); // New PlayAll state (starts from insertionIndex) const [isPlayAllRunning, setIsPlayAllRunning] = React.useState(false); const isPlayAllRunningRef = React.useRef(false); - const currentPlayAllSoundRef = React.useRef(null); - // Ref to hold latest audioContext for cleanup (avoids stale closure) - const audioContextCurrentRef = React.useRef(audioContext); + // Ref to hold latest audio for cleanup (avoids stale closure) + const audioRef = React.useRef(audio); React.useEffect(() => { - audioContextCurrentRef.current = audioContext; - }, [audioContext]); + audioRef.current = audio; + }, [audio]); // Track setTimeout IDs for cleanup const timeoutIdsRef = React.useRef>>( @@ -193,9 +184,6 @@ const RecordingViewSimplified = ({ // Track AbortController for batch loading cleanup const batchLoadingControllerRef = React.useRef(null); - // Single SharedValue for play-all progress (only 1 asset plays at a time) - const playAllProgress = useSharedValue(0); - // Insertion wheel state const [insertionIndex, setInsertionIndex] = React.useState(0); const wheelRef = React.useRef(null); @@ -402,343 +390,123 @@ const RecordingViewSimplified = ({ // AUDIO PLAYBACK // ============================================================================ - // Fetch audio URIs for an asset - // Includes fallback logic for local-only files when server records are removed - const getAssetAudioUris = React.useCallback( - async (assetId: string): Promise => { - try { - // Get content links from both synced and local tables - const assetContentLinkSynced = resolveTable('asset_content_link', { - localOverride: false - }); - const contentLinksSynced = await system.db - .select() - .from(assetContentLinkSynced) - .where(eq(assetContentLinkSynced.asset_id, assetId)); - - const assetContentLinkLocal = resolveTable('asset_content_link', { - localOverride: true - }); - const contentLinksLocal = await system.db - .select() - .from(assetContentLinkLocal) - .where(eq(assetContentLinkLocal.asset_id, assetId)); - - // Prefer synced links, but merge with local for fallback - const allContentLinks = [...contentLinksSynced, ...contentLinksLocal]; + const stopPlayAll = React.useCallback(async () => { + console.log('⏸️ stopPlayAll called'); + isPlayAllRunningRef.current = false; + setIsPlayAllRunning(false); + setCurrentlyPlayingAssetId(null); + await audio.stop(); + }, [audio]); + + const activateAssetForPlayAll = React.useCallback( + async (assetId: string, actualAssetIndex: number) => { + setCurrentlyPlayingAssetId(assetId); + await Promise.resolve(); + + if (wheelRef.current && lastScrolledAssetIdRef.current !== assetId) { + wheelRef.current.scrollItemToTop(actualAssetIndex - 1, true); + lastScrolledAssetIdRef.current = assetId; + } + }, + [] + ); - // Deduplicate by ID (prefer synced over local) - const seenIds = new Set(); - const uniqueLinks = allContentLinks.filter((link) => { - if (seenIds.has(link.id)) { - return false; - } - seenIds.add(link.id); - return true; - }); + /** + * Core queue runner for Play All. + * + * Flow: + * 1) Build ordered playlist from current assets and start index. + * 2) For each asset: activate UI -> play -> wait for playback end. + * 3) Exit immediately if stop was requested. + * 4) Always reset Play All state in finally. + */ + const runPlayAll = React.useCallback( + async (startIndex: number) => { + // Small helper so stop checks read cleanly in the loop. + const isPlayAllActive = () => isPlayAllRunningRef.current; + // Live-array style: iterate directly over assets by index so if assets + // change while Play All is running, queue progression adapts in real time. + if (startIndex >= assets.length) { + console.warn('⚠️ No assets to play from insertion index'); + return; + } - debugLog( - `📀 Found ${uniqueLinks.length} content link(s) for asset ${assetId.slice(0, 8)} (${contentLinksSynced.length} synced, ${contentLinksLocal.length} local)` - ); + // Enter Play All mode once, then keep loop logic simple. + isPlayAllRunningRef.current = true; + setIsPlayAllRunning(true); - if (uniqueLinks.length === 0) { - debugLog('No content links found for asset:', assetId); - return []; - } + debugLog(`▶️ Playing assets sequentially (live list mode)`); - // Get audio values from content links (can be URIs or attachment IDs) - const audioValues = uniqueLinks - .flatMap((link) => { - const audioArray = link.audio ?? []; - debugLog( - ` 📎 Content link has ${audioArray.length} audio file(s):`, - audioArray - ); - return audioArray; - }) - .filter((value): value is string => !!value); + try { + // Sequential queue: each asset fully finishes before next starts. + let index = startIndex; + while (index < assets.length) { + // Stop requested (button toggle, card press, unmount, etc) + if (!isPlayAllActive()) break; + + const item = assets[index]; + if (!item?.id) { + index += 1; + continue; + } + const assetId = item.id; + const actualAssetIndex = index; + // Update list highlight / visibility for this queue position. + await activateAssetForPlayAll(assetId, actualAssetIndex); - debugLog(`📊 Total audio files for asset: ${audioValues.length}`); + debugLog( + `▶️ Playing asset at index ${actualAssetIndex} (${assetId.slice(0, 8)})` + ); - if (audioValues.length === 0) { - debugLog('No audio values found in content links'); - return []; + // Start playback for this asset through AssetAudio abstraction. + await audio.play(assetId); + // Block until this asset is done (natural end or stop). + await audio.waitForPlaybackEnd(assetId); + index += 1; } - - // Process each audio value - can be either a local URI or an attachment ID - const uris: string[] = []; - for (const audioValue of audioValues) { - // Check if this is already a local URI (starts with 'local/' or 'file://') - if (audioValue.startsWith('local/')) { - // It's a direct local URI from saveAudioLocally() - const constructedUri = - await getLocalAttachmentUriWithOPFS(audioValue); - // Check if file exists at constructed path - if (await fileExists(constructedUri)) { - uris.push(constructedUri); - debugLog( - '✅ Using direct local URI:', - constructedUri.slice(0, 80) - ); - } else { - // File doesn't exist at expected path - try to find it in attachment queue - debugLog( - `⚠️ Local URI ${audioValue} not found at ${constructedUri}, searching attachment queue...` - ); - - if (system.permAttachmentQueue) { - // Extract filename from local path (e.g., "local/uuid.wav" -> "uuid.wav") - const filename = audioValue.replace(/^local\//, ''); - // Extract UUID part (without extension) for more flexible matching - const uuidPart = filename.split('.')[0]; - - // Search attachment queue by filename or UUID - let attachment = await system.powersync.getOptional<{ - id: string; - filename: string | null; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE filename = ? OR filename LIKE ? OR id = ? OR id LIKE ? LIMIT 1`, - [filename, `%${uuidPart}%`, filename, `%${uuidPart}%`] - ); - - // If not found, try searching all attachments for this asset's content links - if (!attachment && uniqueLinks.length > 0) { - const allAttachmentIds = uniqueLinks - .flatMap((link) => link.audio ?? []) - .filter( - (av): av is string => - typeof av === 'string' && - !av.startsWith('local/') && - !av.startsWith('file://') - ); - if (allAttachmentIds.length > 0) { - const placeholders = allAttachmentIds - .map(() => '?') - .join(','); - attachment = await system.powersync.getOptional<{ - id: string; - filename: string | null; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE id IN (${placeholders}) LIMIT 1`, - allAttachmentIds - ); - } - } - - if (attachment?.local_uri) { - const foundUri = system.permAttachmentQueue.getLocalUri( - attachment.local_uri - ); - // Verify the found file actually exists - if (await fileExists(foundUri)) { - uris.push(foundUri); - debugLog( - `✅ Found attachment in queue for local URI ${audioValue.slice(0, 20)}` - ); - } else { - debugLog( - `⚠️ Attachment found in queue but file doesn't exist: ${foundUri}` - ); - } - } else { - // Try fallback to local table for alternative audio values - const fallbackLink = contentLinksLocal.find( - (link) => link.asset_id === assetId - ); - if (fallbackLink?.audio) { - for (const fallbackAudioValue of fallbackLink.audio) { - if (fallbackAudioValue.startsWith('file://')) { - if (await fileExists(fallbackAudioValue)) { - uris.push(fallbackAudioValue); - debugLog(`✅ Found fallback file URI`); - break; - } - } - } - } - } - } - } - } else if (audioValue.startsWith('file://')) { - // Already a full file URI - verify it exists - if (await fileExists(audioValue)) { - uris.push(audioValue); - debugLog('✅ Using full file URI:', audioValue.slice(0, 80)); - } else { - debugLog(`⚠️ File URI does not exist: ${audioValue}`); - // Try to find in attachment queue by extracting filename from path - if (system.permAttachmentQueue) { - const filename = audioValue.split('/').pop(); - if (filename) { - const attachment = await system.powersync.getOptional<{ - id: string; - filename: string | null; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE filename = ? OR id = ? LIMIT 1`, - [filename, filename] - ); - - if (attachment?.local_uri) { - const foundUri = system.permAttachmentQueue.getLocalUri( - attachment.local_uri - ); - if (await fileExists(foundUri)) { - uris.push(foundUri); - debugLog(`✅ Found attachment in queue for file URI`); - } - } - } - } - } - } else { - // It's an attachment ID - look it up in the attachment queue - if (!system.permAttachmentQueue) { - // No attachment queue - try fallback to local table - const fallbackLink = contentLinksLocal.find( - (link) => link.asset_id === assetId - ); - if (fallbackLink?.audio) { - for (const fallbackAudioValue of fallbackLink.audio) { - if (fallbackAudioValue.startsWith('local/')) { - const fallbackUri = - await getLocalAttachmentUriWithOPFS(fallbackAudioValue); - if (await fileExists(fallbackUri)) { - uris.push(fallbackUri); - break; - } - } else if (fallbackAudioValue.startsWith('file://')) { - if (await fileExists(fallbackAudioValue)) { - uris.push(fallbackAudioValue); - break; - } - } - } - } - continue; - } - - const attachment = await system.powersync.getOptional<{ - id: string; - local_uri: string | null; - }>( - `SELECT * FROM ${system.permAttachmentQueue.table} WHERE id = ?`, - [audioValue] - ); - - if (attachment?.local_uri) { - const localUri = system.permAttachmentQueue.getLocalUri( - attachment.local_uri - ); - if (await fileExists(localUri)) { - uris.push(localUri); - debugLog('✅ Found attachment URI:', localUri.slice(0, 60)); - } - } else { - // Attachment ID not found in queue - try fallback to local table - debugLog( - `⚠️ Attachment ID ${audioValue.slice(0, 8)} not found in queue, checking local table fallback...` - ); - - const fallbackLink = contentLinksLocal.find( - (link) => link.asset_id === assetId - ); - if (fallbackLink?.audio) { - for (const fallbackAudioValue of fallbackLink.audio) { - if (fallbackAudioValue.startsWith('local/')) { - const fallbackUri = - await getLocalAttachmentUriWithOPFS(fallbackAudioValue); - if (await fileExists(fallbackUri)) { - uris.push(fallbackUri); - debugLog( - `✅ Found fallback local URI for attachment ${audioValue.slice(0, 8)}` - ); - break; - } - } else if (fallbackAudioValue.startsWith('file://')) { - if (await fileExists(fallbackAudioValue)) { - uris.push(fallbackAudioValue); - debugLog( - `✅ Found fallback file URI for attachment ${audioValue.slice(0, 8)}` - ); - break; - } - } - } - } else { - debugLog(`⚠️ Audio ${audioValue} not downloaded yet`); - } - } - } - } - - return uris; - } catch (error) { - console.error('Failed to fetch audio URIs:', error); - return []; + } finally { + // Always leave Play All mode, regardless of completion/cancel/error. + isPlayAllRunningRef.current = false; + setIsPlayAllRunning(false); + setCurrentlyPlayingAssetId(null); } }, - [] + [assets, audio, activateAssetForPlayAll] ); - // Handle asset playback + // Handle asset playback (single asset or merged) const handlePlayAsset = React.useCallback( async (assetId: string) => { try { + // During Play All, any asset tap acts as "stop play all" only. + if (isPlayAllRunningRef.current) { + debugLog('⏸️ Stopping Play All from asset tap:', assetId.slice(0, 8)); + await stopPlayAll(); + return; + } + const isThisAssetPlaying = - audioContext.isPlaying && audioContext.currentAudioId === assetId; + audio.isPlaying && audio.currentAudioId === assetId; if (isThisAssetPlaying) { - debugLog('⏸️ Stopping asset:', assetId.slice(0, 8)); - await audioContext.stopCurrentSound(); + console.log('⏸️ Stopping asset:', assetId.slice(0, 8)); + await audio.stop(); } else { - debugLog('▶️ Playing asset:', assetId.slice(0, 8)); - const uris = await getAssetAudioUris(assetId); - - if (uris.length === 0) { - console.error('❌ No audio URIs found for asset:', assetId); - return; - } - - if (uris.length === 1 && uris[0]) { - debugLog('▶️ Playing single segment'); - await audioContext.playSound(uris[0], assetId); - } else if (uris.length > 1) { - debugLog(`▶️ Playing ${uris.length} segments in sequence`); - await audioContext.playSoundSequence(uris, assetId); - } + console.log('▶️ Playing asset:', assetId.slice(0, 8)); + await audio.play(assetId); } } catch (error) { console.error('❌ Failed to play audio:', error); } }, - [audioContext, getAssetAudioUris] + [audio, stopPlayAll] ); // Handle play all - plays all assets sequentially starting from insertionIndex const handlePlayAll = React.useCallback(async () => { try { - // Check if already playing - toggle to stop if (isPlayAllRunningRef.current) { - isPlayAllRunningRef.current = false; - setIsPlayAllRunning(false); - - // Stop current sound immediately - if (currentPlayAllSoundRef.current) { - try { - await currentPlayAllSoundRef.current.stopAsync(); - await currentPlayAllSoundRef.current.unloadAsync(); - currentPlayAllSoundRef.current = null; - } catch (error) { - console.error('Error stopping sound:', error); - } - } - - // Reset progress - playAllProgress.value = 0; - setCurrentlyPlayingAssetId(null); + await stopPlayAll(); debugLog('⏸️ Stopped play all'); return; } @@ -748,143 +516,14 @@ const RecordingViewSimplified = ({ return; } - // Determine which assets to process starting from insertionIndex const startIndex = Math.min(insertionIndex, assets.length - 1); - const assetsToProcess = assets.slice(startIndex); - - if (assetsToProcess.length === 0) { - console.warn('⚠️ No assets to play from insertion index'); - return; - } - - debugLog( - `🎵 Starting play all from insertion index ${startIndex} (${assetsToProcess.length} assets)...` - ); - - // Mark as running - isPlayAllRunningRef.current = true; - setIsPlayAllRunning(true); - - // Build playlist: Array<{assetId, uris}> - const playlist: { assetId: string; uris: string[] }[] = []; - - for (const asset of assetsToProcess) { - // Check if cancelled - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!isPlayAllRunningRef.current) { - debugLog('⏸️ Play all cancelled during playlist build'); - return; - } - - // Get URIs for this asset - const uris = await getAssetAudioUris(asset.id); - if (uris.length > 0) { - playlist.push({ assetId: asset.id, uris }); - } - } - - if (playlist.length === 0) { - console.error('❌ No audio URIs found for any assets'); - isPlayAllRunningRef.current = false; - setIsPlayAllRunning(false); - return; - } - - debugLog( - `▶️ Playing ${playlist.reduce((sum, p) => sum + p.uris.length, 0)} audio segments from ${playlist.length} assets` - ); - - // Play each asset sequentially - for (let i = 0; i < playlist.length; i++) { - // Check if cancelled - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!isPlayAllRunningRef.current) { - debugLog('⏸️ Play all cancelled'); - setCurrentlyPlayingAssetId(null); - return; - } - - const item = playlist[i]!; - const actualAssetIndex = startIndex + i; - - // HIGHLIGHT THIS ASSET - setCurrentlyPlayingAssetId(item.assetId); - - // Give React a chance to process the state update - await Promise.resolve(); - - // Scroll to this asset in the wheel - // scrollItemToTop adds 1 internally, so subtract 1 to get correct position - if (wheelRef.current) { - wheelRef.current.scrollItemToTop(actualAssetIndex - 1, true); - } - - debugLog( - `▶️ [${i + 1}/${playlist.length}] Playing asset at index ${actualAssetIndex} (${item.assetId.slice(0, 8)}, ${item.uris.length} segments)` - ); - - // Reset progress for new asset - playAllProgress.value = 0; - - // Play all URIs for this asset sequentially - for (const uri of item.uris) { - // Check if cancelled - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!isPlayAllRunningRef.current) { - setCurrentlyPlayingAssetId(null); - return; - } - - // Play this URI and wait for it to finish - await new Promise((resolve) => { - Audio.Sound.createAsync({ uri }, { shouldPlay: true }) - .then(({ sound }) => { - currentPlayAllSoundRef.current = sound; - - sound.setOnPlaybackStatusUpdate((status) => { - if (!status.isLoaded) return; - - // Update progress for current asset - if (status.durationMillis) { - playAllProgress.value = - (status.positionMillis / status.durationMillis) * 100; - } - - if (status.didJustFinish) { - // Mark as complete - playAllProgress.value = 100; - currentPlayAllSoundRef.current = null; - void sound.unloadAsync().then(() => { - resolve(); - }); - } - }); - }) - .catch((error) => { - console.error('Failed to play audio:', error); - currentPlayAllSoundRef.current = null; - resolve(); - }); - }); - } - } - - // Finished playing all - reset progress + await runPlayAll(startIndex); debugLog('✅ Finished playing all assets'); - playAllProgress.value = 0; - setCurrentlyPlayingAssetId(null); - isPlayAllRunningRef.current = false; - setIsPlayAllRunning(false); - currentPlayAllSoundRef.current = null; } catch (error) { console.error('❌ Error playing all assets:', error); - playAllProgress.value = 0; - setCurrentlyPlayingAssetId(null); - isPlayAllRunningRef.current = false; - setIsPlayAllRunning(false); - currentPlayAllSoundRef.current = null; + await stopPlayAll(); } - }, [assets, getAssetAudioUris, insertionIndex, playAllProgress]); + }, [assets.length, insertionIndex, runPlayAll, stopPlayAll]); // ============================================================================ // RECORDING HANDLERS @@ -1378,15 +1017,29 @@ const RecordingViewSimplified = ({ if (audioUri) { // Load audio file to get duration - const { sound } = await Audio.Sound.createAsync({ - uri: audioUri + const player = createAudioPlayer(audioUri); + // Wait for player to load + await new Promise((resolve) => { + if (player.isLoaded) { + resolve(); + return; + } + const check = setInterval(() => { + if (player.isLoaded) { + clearInterval(check); + resolve(); + } + }, 10); + setTimeout(() => { + clearInterval(check); + resolve(); + }, 5000); }); - const status = await sound.getStatusAsync(); - await sound.unloadAsync(); - if (status.isLoaded && status.durationMillis) { - totalDuration += status.durationMillis; + if (player.isLoaded && player.duration > 0) { + totalDuration += player.duration * 1000; } + player.release(); } } catch (err) { // Skip this segment if we can't load it @@ -1599,6 +1252,7 @@ const RecordingViewSimplified = ({ { text: t('merge'), style: 'destructive', + isPreferred: true, onPress: () => { void (async () => { try { @@ -1701,6 +1355,7 @@ const RecordingViewSimplified = ({ { text: t('delete'), style: 'destructive', + isPreferred: true, onPress: () => { void (async () => { try { @@ -1776,42 +1431,23 @@ const RecordingViewSimplified = ({ // This prevents memory leaks when navigating away from the recording view React.useEffect(() => { // Capture refs in variables to avoid stale closure warnings - const assetUriMap = assetUriMapRef.current; - const segmentDurations = segmentDurationsRef.current; - const assetSegmentRanges = assetSegmentRangesRef.current; const pendingAssetNames = pendingAssetNamesRef.current; const loadedAssetIds = loadedAssetIdsRef.current; const timeoutIds = timeoutIdsRef.current; return () => { // Stop audio playback if playing (access via ref for latest state) - if (audioContextCurrentRef.current.isPlaying) { - void audioContextCurrentRef.current.stopCurrentSound(); + if (audioRef.current.isPlaying) { + void audioRef.current.stop(); } // Stop PlayAll if running if (isPlayAllRunningRef.current) { isPlayAllRunningRef.current = false; - - // Stop current sound immediately - if (currentPlayAllSoundRef.current) { - void currentPlayAllSoundRef.current - .stopAsync() - .then(() => { - void currentPlayAllSoundRef.current?.unloadAsync(); - currentPlayAllSoundRef.current = null; - }) - .catch(() => { - // Ignore errors during cleanup - currentPlayAllSoundRef.current = null; - }); - } + void audioRef.current.stop(); } - // Clear all refs to free memory - assetUriMap.clear(); - segmentDurations.length = 0; - assetSegmentRanges.clear(); + // Clear refs to free memory lastScrolledAssetIdRef.current = null; pendingAssetNames.clear(); loadedAssetIds.clear(); @@ -1858,15 +1494,13 @@ const RecordingViewSimplified = ({ const stableHandleRenameAsset = React.useCallback(handleRenameAsset, [ handleRenameAsset ]); - // Memoized render function for LegendList // OPTIMIZED: No audioContext.position dependency - progress now uses SharedValues! // This eliminates 10 re-renders/second during audio playback const renderAssetItem = React.useCallback( ({ item, index }: { item: UIAsset; index: number }) => { - // Check if this asset is playing individually OR if it's the currently playing asset during play-all const isThisAssetPlayingIndividually = - audioContext.isPlaying && audioContext.currentAudioId === item.id; + audio.isPlaying && audio.currentAudioId === item.id; const isThisAssetPlayingInPlayAll = isPlayAllRunning && currentlyPlayingAssetId === item.id; const isThisAssetPlaying = @@ -1878,11 +1512,6 @@ const RecordingViewSimplified = ({ // Duration from lazy-loaded metadata const duration = item.duration; - // Get custom progress for play-all mode (only for the currently playing asset) - const customProgress = isThisAssetPlayingInPlayAll - ? playAllProgress - : undefined; - return ( { if (isSelectionMode) { stableToggleSelect(item.id); @@ -1914,11 +1542,10 @@ const RecordingViewSimplified = ({ ); }, [ - audioContext.isPlaying, - audioContext.currentAudioId, + audio.isPlaying, + audio.currentAudioId, isPlayAllRunning, currentlyPlayingAssetId, - playAllProgress, // audioContext.position REMOVED - uses SharedValues now! // audioContext.duration REMOVED - not needed for render selectedAssetIds, @@ -1938,9 +1565,8 @@ const RecordingViewSimplified = ({ // This eliminates re-creating all children 10+ times per second during audio playback const wheelChildren = React.useMemo(() => { return assetsForLegendList.map((item, index) => { - // Check if this asset is playing individually OR if it's the currently playing asset during play-all const isThisAssetPlayingIndividually = - audioContext.isPlaying && audioContext.currentAudioId === item.id; + audio.isPlaying && audio.currentAudioId === item.id; const isThisAssetPlayingInPlayAll = isPlayAllRunning && currentlyPlayingAssetId === item.id; const isThisAssetPlaying = @@ -1953,11 +1579,6 @@ const RecordingViewSimplified = ({ // Duration from lazy-loaded metadata const duration = item.duration; - // Get custom progress for play-all mode (only for the currently playing asset) - const customProgress = isThisAssetPlayingInPlayAll - ? playAllProgress - : undefined; - return ( { if (isSelectionMode) { stableToggleSelect(item.id); @@ -1991,11 +1611,10 @@ const RecordingViewSimplified = ({ }); }, [ assetsForLegendList, - audioContext.isPlaying, - audioContext.currentAudioId, + audio.isPlaying, + audio.currentAudioId, isPlayAllRunning, currentlyPlayingAssetId, - playAllProgress, // audioContext.position REMOVED - uses SharedValues now! // audioContext.duration REMOVED - not needed for render selectedAssetIds,