Skip to content

Comments

feat(i18n): internationalization of mail app#414

Merged
hiheyhello123 merged 23 commits intoMail-0:stagingfrom
nyzss:feat/i18n
Mar 13, 2025
Merged

feat(i18n): internationalization of mail app#414
hiheyhello123 merged 23 commits intoMail-0:stagingfrom
nyzss:feat/i18n

Conversation

@nyzss
Copy link
Contributor

@nyzss nyzss commented Mar 10, 2025

Description

this PR introduces internationalization (i18n) support

this pr's initial changes have been heavily inspired from #94

changes:

  • sets up i18n using next-intl
  • the whole mail app is covered, only the landing and contributors page remains untouched (though there might be some translations i have missed)

languages added currently:

  • English (en)
  • Turkish (tr)
  • French (fr)
    the translations are currently all AI generated but have been doubled checked by me since they are my native tongues

These changes are only for the mail app.

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🎨 UI/UX improvement

Areas Affected

  • User Interface/Experience

Testing Done

Describe the tests you've done:

  • Manual testing performed
  • Cross-browser testing (if UI changes)

Checklist

  • I have read the CONTRIBUTING document
  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • My changes generate no new warnings

Screenshots/Recordings

https://www.loom.com/share/63bbcd44e0cc4c919891cf9fd0d660b9

final:
https://www.loom.com/share/3ba17f111ad64c8193066c031fc506a8

By submitting this pull request, I confirm that my contribution is made under the terms of the project's license.

Summary by CodeRabbit

  • New Features
    • Introduced comprehensive multi-language support across the mail application.
    • UI elements, error messages, and button labels now dynamically translate based on user language settings.
    • Added a language selection option in settings with support for English, French, Spanish, and Turkish.
    • Updated navigation links and interactive components (e.g., mail composer, theme switcher, search bar) to display localized text, providing a more intuitive and accessible user experience.

@vercel
Copy link

vercel bot commented Mar 10, 2025

@nyzss is attempting to deploy a commit to the Zero Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2025

Walkthrough

The changes integrate internationalization throughout the mail application. Multiple components—including settings pages, mail display, conversation views, UI elements, and layout—now use the next-intl library to replace hardcoded strings with translation keys. New configuration files, types, and localization JSON files (for English, Spanish, French, and Turkish) have been added. Additionally, minor adjustments like external URL updates and import reorganizations have been performed.

Changes

File(s) Summary
apps/mail/app/(routes)/settings/{appearance,connections,general,security,shortcuts}/page.tsx
apps/mail/app/(routes)/settings/[...settings]/page.tsx
apps/mail/app/(error)/not-found.tsx
Integrated useTranslations to replace static texts with dynamic translation keys for titles, descriptions, button labels, and error messages across settings and error pages.
apps/mail/app/layout.tsx Converted RootLayout to an async function and wrapped children with NextIntlClientProvider to enable dynamic locale support.
apps/mail/components/mail/{create-email,mail-display,mail,reply-composer,search-bar,thread-display,mail-iframe}.tsx Added internationalization to mail composing, display, search, thread handling, and iframe loading, replacing static messages with localized keys.
apps/mail/components/{theme/theme-switcher.tsx,ui/app-sidebar.tsx,ui/nav-main.tsx} Updated UI components to use translation functions for labels, theme names, and navigation item titles, and refined badge count handling.
apps/mail/components/connection/add.tsx
apps/mail/components/context/command-palette-context.tsx
apps/mail/components/ui/nav-user.tsx
Enhanced the connection dialog with dynamic translations and updated external documentation URLs from “Mail-0” to “Zero”.
apps/mail/config/{navigation.ts,shortcuts.ts}
apps/mail/global.d.ts
apps/mail/i18n/{config.ts,request.ts,utils.ts}
apps/mail/lib/constants.ts
apps/mail/next.config.ts
apps/mail/package.json
Added new types, constants, configuration files, and dependencies (e.g., deepmerge, next-intl) to support locale management and message merging.
apps/mail/locales/{en.json,es-ES.json,fr.json,tr.json} Introduced new localization files that provide organized key-value pairs for translating various UI elements in multiple languages.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant RL as RootLayout
    participant G as getLocale
    participant GM as getMessages
    participant NI as NextIntlClientProvider
    participant C as Child Component

    U->>RL: Request page
    RL->>G: Fetch locale asynchronously
    G-->>RL: Return locale
    RL->>GM: Retrieve locale messages asynchronously
    GM-->>RL: Return messages object
    RL->>NI: Wrap children with translation context
    NI->>C: Provide localization support via useTranslations
Loading

Possibly related PRs

Suggested reviewers

  • ahmetskilinc
  • MrgSub

Poem

(_/)
( •_•)
Hopping through code with a joyful leap,
I replace static texts so translations run deep.
With every "t()" call, my heart skips a beat,
In every locale, the UI's a treat!
CodeRabbit hops, making language support neat.

Tip

⚡🧪 Multi-step agentic review comment chat (experimental)
  • We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
    - To enable this feature, set early_access to true under in the settings.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a24aab5 and 2c4aec2.

📒 Files selected for processing (2)
  • apps/mail/app/(routes)/settings/general/page.tsx (9 hunks)
  • apps/mail/i18n/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/i18n/utils.ts

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@nyzss nyzss marked this pull request as ready for review March 10, 2025 18:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
apps/mail/components/mail/thread-display.tsx (1)

59-66: Consider localizing the static “Close” tooltip
Line 65 currently displays a hardcoded “Close” label inside <TooltipContent>. Although unchanged in this PR, localizing it would align with your broader i18n approach. For example:

<TooltipContent>{t("common.actions.close")}</TooltipContent>
apps/mail/i18n/utils.ts (1)

7-32: Implement robust locale detection.

The getLocale function correctly follows best practices by:

  1. First checking for user preference in cookies
  2. Falling back to the browser's accept-language header
  3. Providing a default locale as a last resort

However, the parsing logic for accept-language could be more robust.

// extract browser locale from accept-language header
const acceptLanguage = headersList.get("accept-language");
if (acceptLanguage) {
  // it only needs to be the first part of the locale, ex: "en" for "en-US"
-  const reqLocale = acceptLanguage.split(",")[0]?.split("-")[0]?.trim();
+  const reqLocale = acceptLanguage.split(",")[0]?.split("-")[0]?.trim() || "";

  // check if the locale is supported
-  if (locales.includes(reqLocale as Locale)) {
+  if (reqLocale && locales.includes(reqLocale as Locale)) {
    return reqLocale;
  }
}
apps/mail/components/theme/theme-switcher.tsx (1)

61-65: Type-safe translation with dynamic theme key

The component correctly uses the translation function with the current theme value as part of the key path.

Consider creating a helper function to ensure type safety for dynamic keys:

- {t(`common.themes.${theme}` as MessageKey)}
+ {t(`common.themes.${theme as 'dark' | 'light' | 'system'}` as MessageKey)}
apps/mail/app/(routes)/settings/security/page.tsx (1)

15-17: Unnecessary import found

The KeyRound icon is imported but not used in the component.

- import { KeyRound } from "lucide-react";
apps/mail/components/mail/mail.tsx (1)

49-53: Consider using optional chaining as suggested by static analysis

The static analysis tool suggests using optional chaining on line 56 for safer access to potentially undefined values.

-      const filtered = items.filter((item) => item.tags && item.tags.includes(filterTag));
+      const filtered = items.filter((item) => item.tags?.includes(filterTag));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb81ed9 and 7868131.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • apps/mail/app/(routes)/settings/appearance/page.tsx (3 hunks)
  • apps/mail/app/(routes)/settings/connections/page.tsx (5 hunks)
  • apps/mail/app/(routes)/settings/general/page.tsx (8 hunks)
  • apps/mail/app/(routes)/settings/security/page.tsx (5 hunks)
  • apps/mail/app/(routes)/settings/shortcuts/page.tsx (2 hunks)
  • apps/mail/app/layout.tsx (2 hunks)
  • apps/mail/components/connection/add.tsx (2 hunks)
  • apps/mail/components/context/command-palette-context.tsx (7 hunks)
  • apps/mail/components/create/create-email.tsx (10 hunks)
  • apps/mail/components/mail/mail-display.tsx (7 hunks)
  • apps/mail/components/mail/mail.tsx (15 hunks)
  • apps/mail/components/mail/reply-composer.tsx (11 hunks)
  • apps/mail/components/mail/search-bar.tsx (15 hunks)
  • apps/mail/components/mail/thread-display.tsx (6 hunks)
  • apps/mail/components/theme/theme-switcher.tsx (2 hunks)
  • apps/mail/components/ui/app-sidebar.tsx (5 hunks)
  • apps/mail/components/ui/nav-main.tsx (3 hunks)
  • apps/mail/components/ui/nav-user.tsx (5 hunks)
  • apps/mail/config/navigation.ts (5 hunks)
  • apps/mail/config/shortcuts.ts (1 hunks)
  • apps/mail/global.d.ts (1 hunks)
  • apps/mail/i18n/config.ts (1 hunks)
  • apps/mail/i18n/request.ts (1 hunks)
  • apps/mail/i18n/utils.ts (1 hunks)
  • apps/mail/lib/constants.ts (1 hunks)
  • apps/mail/messages/en.json (1 hunks)
  • apps/mail/messages/fr.json (1 hunks)
  • apps/mail/messages/tr.json (1 hunks)
  • apps/mail/next.config.ts (2 hunks)
  • apps/mail/package.json (2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
apps/mail/components/ui/nav-main.tsx

[error] 211-211: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apps/mail/components/mail/mail.tsx

[error] 56-56: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (99)
apps/mail/messages/fr.json (1)

1-209: Well-structured French translations with pluralization support

The French translation file is well-organized with a comprehensive structure covering UI elements across different sections of the application. I particularly appreciate the inclusion of proper pluralization rules for attachments and file counts (lines 85-86), which is crucial for grammatical correctness in French.

apps/mail/lib/constants.ts (1)

1-1: Locale cookie constant follows established naming convention

The new constant for storing locale preferences follows the same naming pattern as other constants in the file and uses a descriptive, namespaced value that clearly indicates its purpose.

apps/mail/global.d.ts (1)

1-8: Type-safe approach for i18n messages

The TypeScript declaration extends the global IntlMessages interface to include the structure of your English messages, providing type safety when accessing translation keys throughout the application. This is a best practice that helps prevent typos and ensures consistent message key usage.

apps/mail/i18n/config.ts (1)

1-4: Clean configuration with type-safe locale definitions

The configuration creates a type-safe union of supported locales derived from the string literals in the array, making it easy to extend with additional languages in the future while maintaining type safety throughout the application.

apps/mail/package.json (1)

56-56: Dependencies added correctly for internationalization.

The addition of deepmerge and next-intl libraries provides the foundation for the internationalization implementation. The versions specified with caret (^) allow for compatible updates, which is good practice.

Also applies to: 72-72

apps/mail/config/shortcuts.ts (1)

3-23: Good refactoring of action strings to camelCase.

Converting the keyboard shortcut action strings to camelCase improves consistency and makes them more suitable as translation keys. This standardization aligns well with the internationalization implementation.

apps/mail/i18n/request.ts (1)

1-16: Well-structured internationalization configuration.

The implementation correctly:

  • Retrieves the current locale
  • Dynamically imports locale-specific messages
  • Uses English messages as a fallback
  • Merges them with deepmerge to ensure complete translations

This approach ensures all UI text can be localized while maintaining fallbacks for missing translations.

apps/mail/next.config.ts (2)

1-1: Next.js configuration properly integrates next-intl.

The integration of next-intl with Next.js is correctly implemented by importing the plugin and wrapping the existing configuration. This enables the internationalization features throughout the application.

Also applies to: 48-50


6-11: Improved formatting of removeConsole config.

The reformatting of the removeConsole configuration enhances readability while maintaining the same functionality.

apps/mail/app/(routes)/settings/appearance/page.tsx (4)

10-10: Import of next-intl library added correctly

Good addition of the next-intl library for internationalization support.


22-22: Translation hook initialized properly

The useTranslations hook is correctly initialized to enable i18n support throughout the component.


42-48: Settings card internationalized appropriately

Great work on replacing hardcoded strings with translation keys for the title, description, and footer button text. The conditional rendering for the button text based on isSaving state is also properly internationalized.


54-55: Theme label internationalized and styling improved

Good job on replacing the hardcoded label text with a translation reference. The background class added to the ModeToggle component improves visual consistency.

apps/mail/components/ui/app-sidebar.tsx (5)

12-16: Imports properly reorganized for i18n support

Good job adding the necessary imports for internationalization support while maintaining proper organization of your imports.


38-40: Improved code formatting for badge count assignment

The logic for setting the badge counts has been reformatted for better readability, using the FOLDERS constant for consistency.


42-44: Consistent formatting applied to SENT folder badge

The same formatting improvement has been applied consistently to the SENT folder badge calculation.


130-130: Translation hook added to ComposeButton component

The useTranslations hook is properly initialized in the ComposeButton function.


142-142: Create button text internationalized

Good replacement of the hardcoded "Create Email" text with a translation key.

apps/mail/components/connection/add.tsx (5)

6-6: Import of next-intl library added correctly

Good addition of the next-intl library for internationalization support.


9-10: Translation hook initialized with proper spacing

The useTranslations hook is correctly initialized with consistent spacing for better readability.


16-16: Add Email text internationalized

Good replacement of the hardcoded "Add email" text with a translation key.


21-22: Dialog title and description internationalized

Excellent replacement of hardcoded dialog title and description text with translation keys.


43-43: Coming Soon text internationalized

Good replacement of the hardcoded "More Coming Soon" text with a translation key.

apps/mail/components/mail/reply-composer.tsx (8)

1-9: Imports reorganized and next-intl added

The imports have been properly reorganized and the necessary useTranslations import has been added for internationalization support.


29-29: Translation hook initialized with namespace

The useTranslations hook is correctly initialized with the "common.replyCompose" namespace, providing better organization for translation keys.


44-44: String quote style standardized

The code style has been improved by using double quotes consistently for string literals, which is a good practice for better readability.

Also applies to: 69-70, 77-78, 85-86


201-212: Empty message check reformatted for clarity

The condition to check for empty messages has been reformatted to be more readable, making the code easier to understand and maintain.


226-228: Reply button text internationalized

Good job replacing the hardcoded "Reply to" text with translation keys that handle both the action and the recipient dynamically.


251-254: Drag and drop area text internationalized

The text for the file drop area has been properly internationalized while maintaining the styling.


317-317: Attachment-related texts internationalized with count parameter

Excellent implementation of internationalized text for attachments, including dynamic count parameters that will support proper pluralization in different languages.

Also applies to: 327-327, 334-334, 336-336


379-379: Action buttons internationalized

Good job internationalizing the "Save Draft" and "Send" button texts, which are critical user interface elements.

Also applies to: 381-382, 392-392

apps/mail/components/mail/thread-display.tsx (5)

21-26: Add import statements for ReplyIcon and useTranslations
The newly introduced imports for ReplyIcon and useTranslations are appropriate to support the animated reply icon and internationalized strings. This aligns well with the i18n enhancements in the rest of your code.


89-89: Minor UI adjustment for <ArchiveIcon>
Using "relative top-0.5" shifts the icon slightly, which can help with visual balance. This looks fine and doesn’t introduce any functional issues.


199-199: Properly initialize translations
Calling useTranslations() and storing it in t is correct for retrieving localized strings throughout the component.


242-295: Internationalization of thread actions in loading skeleton
These lines showcase a good practice of replacing hardcoded labels (e.g., "Close", "Archive", "Reply") with translation keys, ensuring support for multiple locales during the loading state. This approach is consistent and maintains accessibility by pairing each icon with an accessible label.


331-383: Internationalization of thread actions in fully loaded state
All action labels (e.g., "close", "exitFullscreen", "enterFullscreen", "archive", "reply", "moreOptions", "moveToSpam", etc.) now correctly reference translation keys. This is an effective use of next-intl and ensures a seamless experience for multiple locales.

apps/mail/components/ui/nav-user.tsx (2)

31-46: Initialize useTranslations hook
Importing useTranslations and assigning it to t is the standard approach for retrieving localized strings. This is consistent with the rest of your i18n integration.


147-225: Internationalizing menu items
All references to strings such as “customerSupport”, “documentation”, “appTheme”, “accounts”, “logout”, “signIn”, etc. now use translation keys, which is essential for a multilingual experience. This change is implemented cleanly and matches your overall internationalization strategy.

apps/mail/app/layout.tsx (2)

1-4: Importing i18n utilities
Using getLocale and getMessages from "next-intl/server" plus NextIntlClientProvider from "next-intl" sets the foundation for server-side internationalization. This step is crucial for ensuring the client receives the correct locale and message data.


24-39: Adopting an async RootLayout with locale-based <html>
Shifting RootLayout to async to retrieve the user’s locale and messages is a recommended approach for server-driven i18n. Dynamically setting lang={locale} in the <html> tag and wrapping content in NextIntlClientProvider ensures all child components have access to the correct locale and translations.

apps/mail/components/ui/nav-main.tsx (2)

8-13: Imports reorganized to support internationalization.

The imports are now better organized, with the addition of internationalization support through the useTranslations hook from next-intl. The addition of MessageKey type will help ensure type safety when using translation keys.


179-180: Added translation hook for internationalization.

The useTranslations hook has been properly added to access translation functions.

apps/mail/components/context/command-palette-context.tsx (6)

67-68: Added translation hook.

The useTranslations hook has been correctly added to support internationalization in the CommandPalette component.


93-100: Translated command group labels.

Hardcoded group labels have been replaced with translation keys, which is essential for proper internationalization.


103-117: Improved filtering logic with translations.

The filtering logic has been updated to use translations for comparisons and restructured for better readability.


136-141: Translated dialog elements.

The dialog title, description, input placeholder, and empty state message have been properly internationalized.


184-201: Translated help section.

All help-related text has been properly internationalized, including the group heading and command descriptions.


174-174:

Details

❓ Verification inconclusive

Verify translation key consistency.

Unlike in nav-main.tsx where item.title is cast to MessageKey, here it's used directly without type casting. Verify that all item.title values are valid translation keys to prevent runtime errors.


🏁 Script executed:

#!/bin/bash
# Check for any places where item.title might be used with t() function
rg -A 1 -B 1 "t\(item\.title\)" --glob "*.tsx" --glob "*.ts"

# Check for MessageKey imports and usages
rg "MessageKey" --glob "*.tsx" --glob "*.ts"

Length of output: 1830


Action Required: Verify Translation Key Usage in Command Palette Context
In apps/mail/components/context/command-palette-context.tsx on line 174, the translation function is invoked with t(item.title) without casting to MessageKey. In contrast, other parts of the codebase (e.g., in nav-main.tsx) explicitly cast translation keys (i.e., using as MessageKey). Please verify that item.title in this context is always a valid translation key. If there’s any doubt, consider applying the same explicit type cast to ensure type safety and avoid potential runtime errors.

apps/mail/i18n/utils.ts (1)

34-37: Simple and effective locale change function.

The changeLocale function is well implemented, taking the new locale and updating it in the cookie store.

apps/mail/messages/en.json (2)

1-210: Well-structured translation file.

The English translations are organized in a logical, hierarchical structure that makes it easy to locate specific keys. The file includes:

  • Common UI elements and actions
  • Navigation items and settings
  • Page-specific content

The use of ICU MessageFormat for pluralization on lines 86-87 is particularly good practice for internationalization.


86-88: Correctly implemented pluralization.

The use of ICU MessageFormat for plurals is excellent:

"attachmentCount": "{count, plural, =0 {attachments} one {attachment} other {attachments}}",
"fileCount": "{count, plural, =0 {files} one {file} other {files}}",

This ensures proper grammar regardless of the count value.

apps/mail/config/navigation.ts (4)

13-14: Good implementation of type-safe translations

The introduction of MessageKey type using MessageKeys and NestedKeyOf from next-intl provides good type safety for translation keys. This will help catch typos and missing translations at compile time.

Also applies to: 24-24


36-36: Helpful developer comment

The comment clearly instructs developers that navigation item titles must be message keys, which helps maintain consistency across the codebase as internationalization is implemented.


45-78: Consistent internationalization of navigation items

All hardcoded strings have been properly replaced with translation keys, following a structured pattern. This is a good approach for maintainable internationalization.


132-162: Well-structured i18n keys for settings navigation

The settings navigation items have been properly internationalized with a consistent naming pattern (navigation.settings.*), which matches the structure in the translation files.

apps/mail/components/theme/theme-switcher.tsx (2)

12-14: Properly set up internationalization hooks

The component correctly imports and initializes the useTranslations hook from next-intl, and imports the MessageKey type for type safety.

Also applies to: 24-24


73-85: Consistent translations for theme options

All theme options (dark, system, light) now use translations instead of hardcoded text, creating a consistent user experience.

apps/mail/app/(routes)/settings/security/page.tsx (3)

51-53: Properly internationalized title and descriptions

The title and description for the security settings card now correctly use the translation function.

Also applies to: 57-57


55-57: Internationalized button text with conditional rendering

The button text now uses translations and correctly handles the conditional rendering based on the isSaving state.


69-95: Comprehensive internationalization of form fields

All form labels and descriptions have been properly internationalized with appropriate translation keys.

apps/mail/messages/tr.json (2)

1-209: Well-structured Turkish translation file

The translation file is comprehensive and well-organized with clear hierarchical sections for:

  • Common UI elements (actions, themes, command palette)
  • Navigation items
  • Page-specific content
  • Form fields and descriptions

The structure matches the translation keys used in the components, which ensures consistent translations across the application.


84-86: Good use of plural forms

The translation correctly implements plural forms using the ICU message format. This ensures grammatically correct translations for different quantities.

apps/mail/components/create/create-email.tsx (7)

10-10: Added support for internationalization with next-intl

The import of useTranslations from next-intl is a good step for implementing internationalization.


100-100: Good initialization of the translation hook

The useTranslations hook is properly implemented at the component level to access translation strings.


232-232: Replaced hardcoded text with translation key

Correctly using the translation function for the file drop instruction.


246-247: Properly internationalized form labels

All form labels and section headings have been updated to use translation keys, maintaining a consistent approach to internationalization throughout the component.

Also applies to: 292-293, 305-306, 337-338


270-271: Effectively internationalized placeholder texts

Placeholder texts have been successfully updated to use translation keys, which will provide a better user experience for non-English speakers.

Also applies to: 297-298, 312-313


329-330: Correctly implemented pluralized translations

Good use of the count parameter in the translation function for proper pluralization of "file" based on the attachment count.

Also applies to: 340-342


384-385: Successfully internationalized button texts

Button texts have been updated to use translation keys, completing the internationalization of all user-facing strings in the component.

Also applies to: 406-407

apps/mail/app/(routes)/settings/shortcuts/page.tsx (5)

5-7: Added necessary imports for internationalization

Correctly imported both MessageKey for type safety and useTranslations for accessing translated strings.


12-12: Properly initialized translation hook

The translation hook is correctly set up at the component level.


17-19: Successfully internationalized card title and buttons

The card title, description, and button texts have been properly updated to use translation keys.

Also applies to: 21-23


29-30: Smartly implemented dynamic translation keys for shortcuts

Good approach using template literals with the action name to dynamically generate the correct translation key for each shortcut action, with proper type casting to MessageKey.


40-40: Minor class ordering adjustments

The class name reorganization provides better readability without changing the styling.

Also applies to: 46-47

apps/mail/components/mail/mail-display.tsx (7)

3-4: Added and reorganized imports for internationalization

The imports have been reorganized and useTranslations has been added to support internationalization.

Also applies to: 7-7, 10-10


83-91: Improved code formatting for better readability

The ternary operation for data has been properly reformatted with better indentation for improved readability.


91-91: Correctly implemented scoped translations

Good use of the namespaced translation hook with "common.mailDisplay" to keep translations organized and accessible.


139-140: Successfully internationalized email details section

All labels in the email details popover have been properly updated to use translation keys, providing a consistent multilingual experience.

Also applies to: 148-149, 159-160, 171-172, 177-178, 183-184, 189-192


104-105: Improved component structure and class organization

The component's structure and class organization have been improved for better readability and maintainability without affecting functionality.

Also applies to: 107-118, 119-120, 122-126, 128-130


198-205: Enhanced conditional styling with the cn utility

Good use of the cn utility for conditional class application based on the collapsed state.


210-229: Improved conditional rendering of AI summary popup

The conditional rendering of the AI summary popup has been reformatted for better readability while maintaining the same functionality.

apps/mail/app/(routes)/settings/general/page.tsx (7)

25-28: Added essential imports for i18n functionality

Properly imported the necessary i18n utilities including changeLocale, useTranslations, and the available locales.


34-34: Enhanced type safety for language selection

Changed language validation from a simple string to a typed enum using the available locales, which improves type safety and ensures only valid languages can be selected.


59-60: Implemented locale change on form submission

Good addition of the changeLocale function call when submitting the form to apply the selected language immediately.


85-86: Properly initialized translation hook

The translation hook is correctly set up at the component level.


90-92: Successfully internationalized card title and action buttons

The card title, description, and button texts have been properly updated to use translation keys for a consistent multilingual experience.

Also applies to: 96-97, 99-100


112-113: Internationalized form labels and expanded language options

Form labels have been updated to use translation keys, and French and Turkish language options have been added to match the PR objectives.

Also applies to: 122-124, 135-136


163-171: Thoroughly internationalized toggle settings

Toggle settings for dynamic content and external images have been fully internationalized, with both labels and descriptions using translation keys.

Also applies to: 182-190

apps/mail/components/mail/search-bar.tsx (3)

29-29: Good implementation of internationalization hooks

The addition of useTranslations from next-intl and its implementation in both the main component and the DateFilter component is well structured.

Also applies to: 31-32, 102-103


56-56: Well implemented translation for date range picker

The hardcoded text has been properly replaced with a translation key.


550-550: Good job consistently implementing translations throughout the component

All UI text elements have been properly updated to use translation keys, maintaining consistency throughout the search bar component and its child elements.

Also applies to: 568-568, 583-583, 595-595, 603-603, 611-611, 619-619, 628-628, 648-649, 659-660, 668-669, 676-676, 686-686, 699-699, 710-710, 720-720, 731-731, 743-743, 751-751

apps/mail/components/mail/mail.tsx (3)

25-25: Good implementation of internationalization hook

The useTranslations hook is properly imported and implemented in the MailCategoryTabs component.

Also applies to: 448-449


398-434: Well implemented category translations

All category names have been updated to use translation keys instead of hardcoded strings, following good internationalization practices.


550-551: Good implementation of dynamic translation rendering

The component properly renders translated category names using the translation function with appropriate type casting to MessageKey.

Also applies to: 556-557, 585-586

apps/mail/app/(routes)/settings/connections/page.tsx (5)

21-21: Good implementation of internationalization hook

The useTranslations hook is properly imported and implemented in the component.

Also applies to: 31-31


36-36: Well implemented translation for toast messages

Success and error toast messages are properly translated, enhancing the user experience for non-English speakers.

Also applies to: 41-41


47-50: Good implementation of translated card title and description

The SettingsCard component now uses translated strings instead of hardcoded text.


138-141: Well implemented dialog text translations

All dialog text elements have been properly updated to use translation keys, providing a consistent internationalized experience.

Also applies to: 145-152


162-162: Clean implementation of self-closing component

The AddConnectionDialog component is properly implemented as a self-closing tag with appropriate props.

Comment on lines 1 to 210
{
"common": {
"actions": {
"logout": "Logout",
"back": "Back",
"create": "Create Email",
"saveChanges": "Save changes",
"saving": "Saving...",
"resetToDefaults": "Reset to Defaults",
"close": "Close"
},
"themes": {
"dark": "Dark",
"light": "Light",
"system": "System"
},
"commandPalette": {
"title": "Command Palette",
"description": "Quick navigation and actions for Mail-0",
"placeholder": "Type a command or search...",
"noResults": "No results found",
"groups": {
"mail": "Mail",
"settings": "Settings",
"actions": "Actions",
"help": "Help",
"navigation": "Navigation"
},
"commands": {
"goToInbox": "Go to Inbox",
"goToDrafts": "Go to Drafts",
"goToSent": "Go to Sent",
"goToSpam": "Go to Spam",
"goToArchive": "Go to Archive",
"goToBin": "Go to Bin",
"goToSettings": "Go to Settings",
"newEmail": "New Email",
"composeMessage": "Compose message",
"searchEmails": "Search Emails",
"toggleTheme": "Toggle Theme",
"backToMail": "Back to Mail",
"goToDocs": "Go to docs",
"helpWithShortcuts": "Help with shortcuts"
}
},
"searchBar": {
"pickDateRange": "Pick a date or a range",
"search": "Search",
"clearSearch": "Clear search",
"advancedSearch": "Advanced search",
"quickFilters": "Quick filters",
"searchIn": "Search in",
"recipient": "Recipient",
"sender": "Sender",
"subject": "Subject",
"dateRange": "Date range",
"category": "Category",
"folder": "Folder",
"allMail": "All Mail",
"unread": "Unread",
"hasAttachment": "Has Attachment",
"starred": "Starred",
"applyFilters": "Apply filters",
"reset": "Reset"
},
"navUser": {
"customerSupport": "Customer Support",
"documentation": "Documentation",
"appTheme": "App Theme",
"accounts": "Accounts",
"signIn": "Sign in"
},
"mailCategories": {
"primary": "Primary",
"important": "Important",
"personal": "Personal",
"updates": "Updates",
"promotions": "Promotions",
"social": "Social"
},
"replyCompose": {
"replyTo": "Reply to",
"thisEmail": "this email",
"dropFiles": "Drop files to attach",
"attachments": "Attachments",
"attachmentCount": "{count, plural, =0 {attachments} one {attachment} other {attachments}}",
"fileCount": "{count, plural, =0 {files} one {file} other {files}}",
"saveDraft": "Save draft",
"send": "Send"
},
"mailDisplay": {
"details": "Details",
"from": "From",
"to": "To",
"date": "Date",
"mailedBy": "Mailed-By",
"signedBy": "Signed-By",
"security": "Security",
"standardEncryption": "Standard encryption (TLS)"
},
"threadDisplay": {
"exitFullscreen": "Exit fullscreen",
"enterFullscreen": "Enter fullscreen",
"archive": "Archive",
"reply": "Reply",
"moreOptions": "More options",
"moveToSpam": "Move to spam",
"replyAll": "Reply all",
"forward": "Forward",
"markAsUnread": "Mark as unread",
"addLabel": "Add label",
"muteThread": "Mute thread"
}
},
"navigation": {
"sidebar": {
"inbox": "Inbox",
"drafts": "Drafts",
"sent": "Sent",
"spam": "Spam",
"archive": "Archive",
"bin": "Bin",
"settings": "Settings"
},
"settings": {
"general": "General",
"connections": "Connections",
"security": "Security",
"appearance": "Appearance",
"shortcuts": "Shortcuts"
}
},
"pages": {
"settings": {
"general": {
"title": "General",
"description": "Manage settings for your language and email display preferences.",
"language": "Language",
"timezone": "Timezone",
"dynamicContent": "Dynamic Content",
"dynamicContentDescription": "Allow emails to display dynamic content.",
"externalImages": "Display External Images",
"externalImagesDescription": "Allow emails to display images from external sources."
},
"connections": {
"title": "Email Connections",
"description": "Connect your email accounts to Zero.",
"disconnectTitle": "Disconnect Email Account",
"disconnectDescription": "Are you sure you want to disconnect this email?",
"cancel": "Cancel",
"remove": "Remove",
"disconnectSuccess": "Account disconnected successfully",
"disconnectError": "Failed to disconnect account",
"addEmail": "Add Email",
"connectEmail": "Connect Email",
"connectEmailDescription": "Select an email provider to connect",
"moreComingSoon": "More coming soon"
},
"security": {
"title": "Security",
"description": "Manage your security preferences and account protection.",
"twoFactorAuth": "Two-Factor Authentication",
"twoFactorAuthDescription": "Add an extra layer of security to your account",
"loginNotifications": "Login Notifications",
"loginNotificationsDescription": "Get notified about new login attempts",
"deleteAccount": "Delete Account"
},
"appearance": {
"title": "Appearance",
"description": "Customize colors, fonts and view options.",
"theme": "Theme",
"inboxType": "Inbox Type"
},
"shortcuts": {
"title": "Keyboard Shortcuts",
"description": "View and customize keyboard shortcuts for quick actions.",
"actions": {
"newEmail": "New Email",
"sendEmail": "Send Email",
"reply": "Reply",
"replyAll": "Reply All",
"forward": "Forward",
"drafts": "Drafts",
"inbox": "Inbox",
"sentMail": "Sent Mail",
"delete": "Delete",
"search": "Search",
"markAsUnread": "Mark as Unread",
"muteThread": "Mute Thread",
"printEmail": "Print Email",
"archiveEmail": "Archive Email",
"markAsSpam": "Mark as Spam",
"moveToFolder": "Move to Folder",
"undoLastAction": "Undo Last Action",
"viewEmailDetails": "View Email Details",
"goToDrafts": "Go to Drafts",
"expandEmailView": "Expand Email View",
"helpWithShortcuts": "Help with shortcuts"
}
}
},
"createEmail": {
"body": "Body",
"example": "zero@0.email",
"attachments": "Attachments",
"dropFilesToAttach": "Drop files to attach",
"writeYourMessageHere": "Write your message here..."
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify coverage of all UI text.

This appears to be a comprehensive translation file, but it would be good to verify that all visible text in the application is covered by these translation keys. Consider running a verification to check for any hardcoded strings that still need to be internationalized.


🏁 Script executed:

#!/bin/bash
# Look for potential hardcoded strings in components
# This searches for JSX text content that might not be using the translation function

# Look for text within JSX tags that doesn't use translation
rg "<[^>]*>([^<{}]*[a-zA-Z]{3,}[^<{}]*)</[^>]*>" --glob "*.tsx" | grep -v "t("

# Look for string literals in UI components that might need translation
rg "label=\"[^\"]+\"" --glob "*.tsx" | grep -v "t("
rg "placeholder=\"[^\"]+\"" --glob "*.tsx" | grep -v "t("
rg "title=\"[^\"]+\"" --glob "*.tsx" | grep -v "t("

Length of output: 18902


Action Required: Audit Hardcoded UI Strings for Internationalization

The automated search has revealed several instances of hardcoded text in various UI components that aren't wrapped in our translation function. While the translation file in apps/mail/messages/en.json is comprehensive, many visible texts remain unlocalized. Please review the following key areas and update them to use translation keys where user-facing text is involved:

  • Error & Status Messages:

    • apps/mail/app/error.tsx – "Something went wrong!" and "See the console for more information."
    • apps/mail/app/(error)/not-found.tsx – "Page Not Found"
  • Authentication & Account Setup:

    • apps/mail/app/(auth)/signup/page.tsx – "Create your 0.email account"
    • apps/mail/app/(auth)/signup/verify/page.tsx – "Verify your email"
    • apps/mail/app/(auth)/login/page.tsx – "Welcome to 0"
  • Navigation and Sidebar:

    • apps/mail/components/ui/sidebar.tsx – Labels like "Toggle Sidebar" appear both as visible text and in aria attributes.
  • Miscellaneous UI Components:

    • Text in components such as envelop.tsx, command.tsx, home/hero.tsx, and others (e.g., various button labels, placeholders, titles, and descriptive texts in forms and dialogs).

Next Steps:

  • Verify if each hardcoded string is intended as user-visible content.
  • For those that are user-facing, refactor the code to use the internationalization function (e.g., t("...")) and add the corresponding key/value pair in the translation file if not already present.

This will help ensure consistency and full localization coverage across the application.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (28)
apps/mail/messages/en.json (1)

45-51: ⚠️ Potential issue

Duplicate navigation user entries

These navUser entries duplicate what's already defined on lines 66-72. This creates potential maintenance issues if translations need to be updated in the future.

To avoid duplication, consider removing this redundant section or refactoring to use a single source of truth for these translations.

-    "navUser": {
-      "customerSupport": "Customer Support",
-      "documentation": "Documentation",
-      "appTheme": "App Theme",
-      "accounts": "Accounts",
-      "signIn": "Sign in"
-    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

{
  ...
  "searchBar": {
    "pickDateRange": "Pick a date or a range",
    "search": "Search",
    "clearSearch": "Clear search",
    "advancedSearch": "Advanced search",
    "quickFilters": "Quick filters"
  },
-  "navUser": {
-    "customerSupport": "Customer Support",
-    "documentation": "Documentation",
-    "appTheme": "App Theme",
-    "accounts": "Accounts",
-    "signIn": "Sign in"
-  },
  ...
  // The remaining code continues here. The duplicate "navUser" block defined on lines 66-72 remains as the single source of truth.
}
apps/mail/app/(auth)/zero/login/page.tsx (5)

87-98: 🛠️ Refactor suggestion

Internationalize login button and signup link text.

Complete the internationalization for the form button and signup link.

            <Button type="submit" className="w-full">
-              Login
+              {t("common.actions.login")}
            </Button>

            <div className="mt-6 text-center text-sm">
              <p className="text-muted-foreground">
-                Don't have an account?{" "}
+                {t("pages.login.noAccount")}{" "}
                <Link href="/zero/signup" className="text-white underline hover:text-white/80">
-                  Sign up
+                  {t("common.actions.signup")}
                </Link>
              </p>
            </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            <Button type="submit" className="w-full">
              {t("common.actions.login")}
            </Button>

            <div className="mt-6 text-center text-sm">
              <p className="text-muted-foreground">
                {t("pages.login.noAccount")}{" "}
                <Link href="/zero/signup" className="text-white underline hover:text-white/80">
                  {t("common.actions.signup")}
                </Link>
              </p>
            </div>

43-61: 🛠️ Refactor suggestion

Update form field labels and placeholders with translations.

Continue with the internationalization of form field elements.

        <Form {...form}>
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3">
            <FormField
              control={form.control}
              name="email"
              render={({ field }) => (
                <FormItem>
-                  <FormLabel className="text-muted-foreground">Email</FormLabel>
+                  <FormLabel className="text-muted-foreground">{t("common.fields.email")}</FormLabel>
                  <FormControl>
                    <Input
-                      placeholder="nizzy@0.email"
+                      placeholder={t("pages.login.emailPlaceholder")}
                      {...field}
                      className="bg-black text-sm text-white placeholder:text-sm"
                    />
                  </FormControl>
                </FormItem>
              )}
            />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        <Form {...form}>
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3">
            <FormField
              control={form.control}
              name="email"
              render={({ field }) => (
                <FormItem>
                  <FormLabel className="text-muted-foreground">{t("common.fields.email")}</FormLabel>
                  <FormControl>
                    <Input
                      placeholder={t("pages.login.emailPlaceholder")}
                      {...field}
                      className="bg-black text-sm text-white placeholder:text-sm"
                    />
                  </FormControl>
                </FormItem>
              )}
            />

104-118: 🛠️ Refactor suggestion

Internationalize footer links.

Add translations for the footer links to Terms of Service and Privacy Policy.

      <footer className="absolute bottom-0 w-full py-4 px-6">
        <div className="max-w-6xl mx-auto flex justify-center items-center gap-6">
          <a 
            href="/terms" 
            className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors"
          >
-            Terms of Service
+            {t("common.footer.terms")}
          </a>
          <a 
            href="/privacy" 
            className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors"
          >
-            Privacy Policy
+            {t("common.footer.privacy")}
          </a>
        </div>
      </footer>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      <footer className="absolute bottom-0 w-full py-4 px-6">
        <div className="max-w-6xl mx-auto flex justify-center items-center gap-6">
          <a 
            href="/terms" 
            className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors"
          >
            {t("common.footer.terms")}
          </a>
          <a 
            href="/privacy" 
            className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors"
          >
            {t("common.footer.privacy")}
          </a>
        </div>
      </footer>

62-86: 🛠️ Refactor suggestion

Internationalize password field and forgot password link.

Add translations for the password section of the form.

            <FormField
              control={form.control}
              name="password"
              render={({ field }) => (
                <FormItem>
                  <div className="flex items-center justify-between">
-                    <FormLabel className="text-muted-foreground">Password</FormLabel>
+                    <FormLabel className="text-muted-foreground">{t("common.fields.password")}</FormLabel>
                    <Link
                      href="/forgot-password"
                      className="text-muted-foreground text-xs hover:text-white"
                    >
-                      Forgot your password?
+                      {t("pages.login.forgotPassword")}
                    </Link>
                  </div>
                  <FormControl>
                    <Input
                      type="password"
-                      placeholder="••••••••"
+                      placeholder={t("common.fields.passwordPlaceholder")}
                      {...field}
                      className="bg-black text-white"
                    />
                  </FormControl>
                </FormItem>
              )}
            />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            <FormField
              control={form.control}
              name="password"
              render={({ field }) => (
                <FormItem>
                  <div className="flex items-center justify-between">
                    <FormLabel className="text-muted-foreground">{t("common.fields.password")}</FormLabel>
                    <Link
                      href="/forgot-password"
                      className="text-muted-foreground text-xs hover:text-white"
                    >
                      {t("pages.login.forgotPassword")}
                    </Link>
                  </div>
                  <FormControl>
                    <Input
                      type="password"
                      placeholder={t("common.fields.passwordPlaceholder")}
                      {...field}
                      className="bg-black text-white"
                    />
                  </FormControl>
                </FormItem>
              )}
            />

1-41: 🛠️ Refactor suggestion

Add internationalization support for text strings.

This file is part of a PR that adds i18n support, but the component text is hardcoded. Consider using the useTranslations hook from next-intl for text elements like headings, labels, and placeholder text.

+ import { useTranslations } from "next-intl";

export default function LoginZero() {
+  const t = useTranslations();
  const form = useForm<z.infer<typeof formSchema>>({
    // ...
  });
  
  // ...
  
  return (
    <div className="flex h-full min-h-screen w-full items-center justify-center bg-black">
      <div className="w-full max-w-md px-6 py-8 animate-in slide-in-from-bottom-4 duration-500">
        <div className="mb-4 text-center">
-          <h1 className="mb-2 text-4xl font-bold text-white">Login with Zero</h1>
-          <p className="text-muted-foreground">Enter your Zero email below to login to your account</p>
+          <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.login.title")}</h1>
+          <p className="text-muted-foreground">{t("pages.login.description")}</p>
        </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

"use client";

import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import Link from "next/link";
import { z } from "zod";
import { useTranslations } from "next-intl";

const formSchema = z.object({
  email: z.string().email({ message: "Please enter a valid email address" }),
  password: z.string().min(6, { message: "Password must be at least 6 characters" }),
});

export default function LoginZero() {
  const t = useTranslations();
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      email: "",
      password: "",
    },
  });

  function onSubmit(values: z.infer<typeof formSchema>) {
    // Use the correct sonner toast API
    toast.success(`Trying to log in with ${values.email}`, {
      description: "Login attempt",
    });

    // Here you would typically handle authentication
  }

  return (
    <div className="flex h-full min-h-screen w-full items-center justify-center bg-black">
      <div className="w-full max-w-md px-6 py-8 animate-in slide-in-from-bottom-4 duration-500">
        <div className="mb-4 text-center">
          <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.login.title")}</h1>
          <p className="text-muted-foreground">{t("pages.login.description")}</p>
        </div>
        {/* Remaining component JSX */}
      </div>
    </div>
  );
}
apps/mail/components/theme/theme-toggle.tsx (2)

1-18: 🛠️ Refactor suggestion

Implement internationalization for the toggle theme text.

This component should use the useTranslations hook for the toggle theme label to align with the PR's goal of adding i18n support.

"use client";

import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { MoonIcon } from "@/components/icons/animated/moon";
import { SunIcon } from "@/components/icons/animated/sun";
+ import { useTranslations } from "next-intl";

interface ThemeToggleProps {
  className?: string;
  showLabel?: boolean;
}

export function ThemeToggle({ className = "", showLabel = false }: ThemeToggleProps) {
  const [isRendered, setIsRendered] = useState(false);
  const { theme, resolvedTheme, setTheme } = useTheme();
+  const t = useTranslations();

  useEffect(() => setIsRendered(true), []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

"use client";

import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { MoonIcon } from "@/components/icons/animated/moon";
import { SunIcon } from "@/components/icons/animated/sun";
import { useTranslations } from "next-intl";

interface ThemeToggleProps {
  className?: string;
  showLabel?: boolean;
}

export function ThemeToggle({ className = "", showLabel = false }: ThemeToggleProps) {
  const [isRendered, setIsRendered] = useState(false);
  const { theme, resolvedTheme, setTheme } = useTheme();
  const t = useTranslations();

  useEffect(() => setIsRendered(true), []);

36-46: 🛠️ Refactor suggestion

Update the toggle theme button label with translation.

Replace the hardcoded label with a translated text.

  if (!isRendered) return null;

  return (
    <button
      onClick={handleThemeToggle}
      className={`flex items-center rounded-md p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white transition-all ${className}`}
-      aria-label="Toggle theme"
+      aria-label={t("common.theme.toggle")}
    >
      {theme === "dark" ? <MoonIcon className="opacity-60" /> : <SunIcon className="opacity-60" />}
-      {showLabel && <span className="ml-2 text-sm">Toggle theme</span>}
+      {showLabel && <span className="ml-2 text-sm">{t("common.theme.toggle")}</span>}
    </button>
  );
} 
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  if (!isRendered) return null;

  return (
    <button
      onClick={handleThemeToggle}
      className={`flex items-center rounded-md p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white transition-all ${className}`}
      aria-label={t("common.theme.toggle")}
    >
      {theme === "dark" ? <MoonIcon className="opacity-60" /> : <SunIcon className="opacity-60" />}
      {showLabel && <span className="ml-2 text-sm">{t("common.theme.toggle")}</span>}
    </button>
  );
apps/mail/components/create/editor-buttons.tsx (7)

179-183: ⚠️ Potential issue

Fix empty EditorProvider implementation.

The default export contains an empty EditorProvider with no children or editor configuration.

export default () => {
  return (
-    <EditorProvider slotBefore={<MenuBar />}></EditorProvider>
+    <EditorProvider
+      slotBefore={<MenuBar />}
+      extensions={[/* Add required extensions here */]}
+    >
+      {/* Add editor content or children here */}
+    </EditorProvider>
  )
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export default () => {
  return (
    <EditorProvider
      slotBefore={<MenuBar />}
      extensions={[/* Add required extensions here */]}
    >
      {/* Add editor content or children here */}
    </EditorProvider>
  )
}

1-3: 🛠️ Refactor suggestion

Add internationalization support for editor buttons.

This component should use translations for button labels to align with the PR's internationalization goals.

import { EditorProvider, useCurrentEditor } from '@tiptap/react'
import React from 'react'
+ import { useTranslations } from 'next-intl'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

import { EditorProvider, useCurrentEditor } from '@tiptap/react'
import React from 'react'
import { useTranslations } from 'next-intl'

11-27: 🛠️ Refactor suggestion

Replace hardcoded button labels with translations.

Start updating button labels with translation keys.

  return (
    <div className="control-group">
      <div className="button-group">
        <button
          onClick={() => editor.chain().focus().toggleBold().run()}
          disabled={
            !editor.can()
              .chain()
              .focus()
              .toggleBold()
              .run()
          }
          className={editor.isActive('bold') ? 'is-active' : ''}
        >
-          Bold
+          {t("editor.formatting.bold")}
        </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  return (
    <div className="control-group">
      <div className="button-group">
        <button
          onClick={() => editor.chain().focus().toggleBold().run()}
          disabled={
            !editor.can()
              .chain()
              .focus()
              .toggleBold()
              .run()
          }
          className={editor.isActive('bold') ? 'is-active' : ''}
        >
          {t("editor.formatting.bold")}
        </button>
        <button

27-65: 🛠️ Refactor suggestion

Continue replacing button labels with translations.

Update more button labels with translation keys.

        <button
          onClick={() => editor.chain().focus().toggleItalic().run()}
          disabled={
            !editor.can()
              .chain()
              .focus()
              .toggleItalic()
              .run()
          }
          className={editor.isActive('italic') ? 'is-active' : ''}
        >
-          Italic
+          {t("editor.formatting.italic")}
        </button>
        <button
          onClick={() => editor.chain().focus().toggleStrike().run()}
          disabled={
            !editor.can()
              .chain()
              .focus()
              .toggleStrike()
              .run()
          }
          className={editor.isActive('strike') ? 'is-active' : ''}
        >
-          Strike
+          {t("editor.formatting.strike")}
        </button>
        <button
          onClick={() => editor.chain().focus().toggleCode().run()}
          disabled={
            !editor.can()
              .chain()
              .focus()
              .toggleCode()
              .run()
          }
          className={editor.isActive('code') ? 'is-active' : ''}
        >
-          Code
+          {t("editor.formatting.code")}
        </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        <button
          onClick={() => editor.chain().focus().toggleItalic().run()}
          disabled={
            !editor.can()
              .chain()
              .focus()
              .toggleItalic()
              .run()
          }
          className={editor.isActive('italic') ? 'is-active' : ''}
        >
          {t("editor.formatting.italic")}
        </button>
        <button
          onClick={() => editor.chain().focus().toggleStrike().run()}
          disabled={
            !editor.can()
              .chain()
              .focus()
              .toggleStrike()
              .run()
          }
          className={editor.isActive('strike') ? 'is-active' : ''}
        >
          {t("editor.formatting.strike")}
        </button>
        <button
          onClick={() => editor.chain().focus().toggleCode().run()}
          disabled={
            !editor.can()
              .chain()
              .focus()
              .toggleCode()
              .run()
          }
          className={editor.isActive('code') ? 'is-active' : ''}
        >
          {t("editor.formatting.code")}
        </button>

4-10: 🛠️ Refactor suggestion

Update MenuBar component to use translations.

Add translation functionality to the MenuBar component.

const MenuBar = () => {
  const { editor } = useCurrentEditor()
+  const t = useTranslations()

  if (!editor) {
    return null
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const MenuBar = () => {
  const { editor } = useCurrentEditor()
  const t = useTranslations()

  if (!editor) {
    return null
  }

66-77: 🛠️ Refactor suggestion

Update remaining button labels with translations.

Continue replacing hardcoded text with translation keys.

        <button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
-          Clear marks
+          {t("editor.actions.clearMarks")}
        </button>
        <button onClick={() => editor.chain().focus().clearNodes().run()}>
-          Clear nodes
+          {t("editor.actions.clearNodes")}
        </button>
        <button
          onClick={() => editor.chain().focus().setParagraph().run()}
          className={editor.isActive('paragraph') ? 'is-active' : ''}
        >
-          Paragraph
+          {t("editor.blocks.paragraph")}
        </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        <button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
          {t("editor.actions.clearMarks")}
        </button>
        <button onClick={() => editor.chain().focus().clearNodes().run()}>
          {t("editor.actions.clearNodes")}
        </button>
        <button
          onClick={() => editor.chain().focus().setParagraph().run()}
          className={editor.isActive('paragraph') ? 'is-active' : ''}
        >
          {t("editor.blocks.paragraph")}
        </button>

77-174: 🛠️ Refactor suggestion

Continue replacing remaining text with translations.

Complete the internationalization of button labels.

Implementing all translations would be lengthy, but here's a pattern to follow for the remaining buttons:

        <button
          onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
          className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
        >
-          H1
+          {t("editor.blocks.h1")}
        </button>
        
        // Continue the same pattern for all remaining buttons
        // ...
        
        <button
          onClick={() => editor.chain().focus().setColor('#958DF1').run()}
          className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''}
        >
-          Purple
+          {t("editor.colors.purple")}
        </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        </button>
        <button
          onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
          className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
        >
          {t("editor.blocks.h1")}
        </button>
        <button
          onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
          className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
        >
          H2
        </button>
        <button
          onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
          className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
        >
          H3
        </button>
        <button
          onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
          className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
        >
          H4
        </button>
        <button
          onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
          className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
        >
          H5
        </button>
        <button
          onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
          className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
        >
          H6
        </button>
        <button
          onClick={() => editor.chain().focus().toggleBulletList().run()}
          className={editor.isActive('bulletList') ? 'is-active' : ''}
        >
          Bullet list
        </button>
        <button
          onClick={() => editor.chain().focus().toggleOrderedList().run()}
          className={editor.isActive('orderedList') ? 'is-active' : ''}
        >
          Ordered list
        </button>
        <button
          onClick={() => editor.chain().focus().toggleCodeBlock().run()}
          className={editor.isActive('codeBlock') ? 'is-active' : ''}
        >
          Code block
        </button>
        <button
          onClick={() => editor.chain().focus().toggleBlockquote().run()}
          className={editor.isActive('blockquote') ? 'is-active' : ''}
        >
          Blockquote
        </button>
        <button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
          Horizontal rule
        </button>
        <button onClick={() => editor.chain().focus().setHardBreak().run()}>
          Hard break
        </button>
        <button
          onClick={() => editor.chain().focus().undo().run()}
          disabled={
            !editor.can()
              .chain()
              .focus()
              .undo()
              .run()
          }
        >
          Undo
        </button>
        <button
          onClick={() => editor.chain().focus().redo().run()}
          disabled={
            !editor.can()
              .chain()
              .focus()
              .redo()
              .run()
          }
        >
          Redo
        </button>
        <button
          onClick={() => editor.chain().focus().setColor('#958DF1').run()}
          className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''}
        >
          {t("editor.colors.purple")}
        </button>
      </div>
apps/mail/app/(auth)/zero/signup/page.tsx (8)

39-45: 🛠️ Refactor suggestion

Add internationalization to heading and description text.

Replace hardcoded text with translation keys.

  return (
    <div className="flex h-full min-h-screen w-full items-center justify-center bg-black">
      <div className="w-full max-w-md px-6 py-8 animate-in slide-in-from-bottom-4 duration-500">
        <div className="mb-4 text-center">
-          <h1 className="mb-2 text-4xl font-bold text-white">Signup with Zero</h1>
-          <p className="text-muted-foreground">Enter your email below to signup to your account</p>
+          <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.signup.title")}</h1>
+          <p className="text-muted-foreground">{t("pages.signup.description")}</p>
        </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  return (
    <div className="flex h-full min-h-screen w-full items-center justify-center bg-black">
      <div className="w-full max-w-md px-6 py-8 animate-in slide-in-from-bottom-4 duration-500">
        <div className="mb-4 text-center">
          <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.signup.title")}</h1>
          <p className="text-muted-foreground">{t("pages.signup.description")}</p>
        </div>

123-136: 🛠️ Refactor suggestion

Internationalize footer links.

Add translations for the Terms of Service and Privacy Policy links.

      <footer className="absolute bottom-0 w-full py-4 px-6">
        <div className="max-w-6xl mx-auto flex justify-center items-center gap-6">
          <a 
            href="/terms" 
            className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors"
          >
-            Terms of Service
+            {t("common.footer.terms")}
          </a>
          <a 
            href="/privacy" 
            className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors"
          >
-            Privacy Policy
+            {t("common.footer.privacy")}
          </a>
        </div>
      </footer>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      <footer className="absolute bottom-0 w-full py-4 px-6">
        <div className="max-w-6xl mx-auto flex justify-center items-center gap-6">
          <a 
            href="/terms" 
            className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors"
          >
            {t("common.footer.terms")}
          </a>
          <a 
            href="/privacy" 
            className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors"
          >
            {t("common.footer.privacy")}
          </a>
        </div>
      </footer>

49-64: 🛠️ Refactor suggestion

Internationalize form field labels and placeholders.

Update the name field with translations.

                <FormItem>
-                  <FormLabel className="text-muted-foreground">Name</FormLabel>
+                  <FormLabel className="text-muted-foreground">{t("common.fields.name")}</FormLabel>
                  <FormControl>
                    <Input
-                      placeholder="Luke"
+                      placeholder={t("pages.signup.namePlaceholder")}
                      {...field}
                      className="bg-black text-sm text-white placeholder:text-sm"
                    />
                  </FormControl>
                </FormItem>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            <FormField
              control={form.control}
              name="name"
              render={({ field }) => (
                <FormItem>
                  <FormLabel className="text-muted-foreground">{t("common.fields.name")}</FormLabel>
                  <FormControl>
                    <Input
                      placeholder={t("pages.signup.namePlaceholder")}
                      {...field}
                      className="bg-black text-sm text-white placeholder:text-sm"
                    />
                  </FormControl>
                </FormItem>
              )}
            />

85-105: 🛠️ Refactor suggestion

Internationalize password field.

Update the password field with translations.

                <FormItem>
                  <div className="flex items-center justify-between">
-                    <FormLabel className="text-muted-foreground">Password</FormLabel>
+                    <FormLabel className="text-muted-foreground">{t("common.fields.password")}</FormLabel>
                   
                  </div>
                  <FormControl>
                    <Input
                      type="password"
-                      placeholder="••••••••"
+                      placeholder={t("common.fields.passwordPlaceholder")}
                      {...field}
                      className="bg-black text-white"
                    />
                  </FormControl>
                </FormItem>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            <FormField
              control={form.control}
              name="password"
              render={({ field }) => (
                <FormItem>
                  <div className="flex items-center justify-between">
                    <FormLabel className="text-muted-foreground">{t("common.fields.password")}</FormLabel>
                  </div>
                  <FormControl>
                    <Input
                      type="password"
                      placeholder={t("common.fields.passwordPlaceholder")}
                      {...field}
                      className="bg-black text-white"
                    />
                  </FormControl>
                </FormItem>
              )}
            />

106-118: 🛠️ Refactor suggestion

Fix contradictory text and add internationalization for button and link.

The text incorrectly says "Don't have an account?" but then provides a login link. Fix this and add translations.

            <Button type="submit" className="w-full">
-              Signup
+              {t("common.actions.signup")}
            </Button>

            <div className="mt-6 text-center text-sm">
              <p className="text-muted-foreground">
-                Don't have an account?{" "}
+                {t("pages.signup.alreadyHaveAccount")}{" "}
                <Link href="/zero/login" className="text-white underline hover:text-white/80">
-                  Login
+                  {t("common.actions.login")}
                </Link>
              </p>
            </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            <Button type="submit" className="w-full">
              {t("common.actions.signup")}
            </Button>

            <div className="mt-6 text-center text-sm">
              <p className="text-muted-foreground">
                {t("pages.signup.alreadyHaveAccount")}{" "}
                <Link href="/zero/login" className="text-white underline hover:text-white/80">
                  {t("common.actions.login")}
                </Link>
              </p>
            </div>

65-84: 🛠️ Refactor suggestion

Internationalize email field and domain suffix.

Update the email field with translations.

                <FormItem>
-                  <FormLabel className="text-muted-foreground">Email</FormLabel>
+                  <FormLabel className="text-muted-foreground">{t("common.fields.email")}</FormLabel>
                  <FormControl>
                    <div className="relative w-full rounded-md">
                      <Input
-                        placeholder="adam"
+                        placeholder={t("pages.signup.emailPlaceholder")}
                        {...field}
                        className="bg-black text-sm text-white placeholder:text-sm w-full pr-16"
                      />
                      <span className="absolute bg-popover px-3 py-2 right-0 top-0 bottom-0 flex items-center text-sm rounded-r-md text-muted-foreground border border-l-0 border-input">@0.email</span>
                    </div>
                  </FormControl>
                </FormItem>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            <FormField
              control={form.control}
              name="email"
              render={({ field }) => (
                <FormItem>
                  <FormLabel className="text-muted-foreground">{t("common.fields.email")}</FormLabel>
                  <FormControl>
                    <div className="relative w-full rounded-md">
                      <Input
                        placeholder={t("pages.signup.emailPlaceholder")}
                        {...field}
                        className="bg-black text-sm text-white placeholder:text-sm w-full pr-16"
                      />
                      <span className="absolute bg-popover px-3 py-2 right-0 top-0 bottom-0 flex items-center text-sm rounded-r-md text-muted-foreground border border-l-0 border-input">@0.email</span>
                    </div>
                  </FormControl>
                </FormItem>
              )}
            />

26-37: 🛠️ Refactor suggestion

Update onSubmit function and fix invalid text in signup message.

The signup text incorrectly says "Don't have an account?" but links to login. Also, add internationalization for toast messages.

  function onSubmit(values: z.infer<typeof formSchema>) {
    // Append the @0.email suffix to the username
    const fullEmail = `${values.email}@0.email`;
    
    // Use the correct sonner toast API
    toast.success(`Trying to signup with ${fullEmail}`, {
-      description: "Signup attempt",
+      description: t("pages.signup.toasts.attemptDescription"),
    });

    // Here you would typically handle authentication with the full email
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  function onSubmit(values: z.infer<typeof formSchema>) {
    // Append the @0.email suffix to the username
    const fullEmail = `${values.email}@0.email`;
    
    // Use the correct sonner toast API
    toast.success(`Trying to signup with ${fullEmail}`, {
      description: t("pages.signup.toasts.attemptDescription"),
    });

    // Here you would typically handle authentication with the full email
  }

12-16: 🛠️ Refactor suggestion

Update email validation and add appropriate internationalization.

The email field validation only checks for minimum length, not format. Also, all schema error messages should be internationalized.

+ import { useTranslations } from "next-intl";

const formSchema = z.object({
-  name: z.string().min(1, { message: "Name must be at least 1 character" }),
-  email: z.string().min(1, { message: "Username must be at least 1 character" }),
-  password: z.string().min(6, { message: "Password must be at least 6 characters" }),
+  name: z.string().min(1, { message: "pages.signup.errors.nameRequired" }),
+  email: z.string()
+    .min(1, { message: "pages.signup.errors.usernameRequired" })
+    .refine(email => !email.includes('@'), { message: "pages.signup.errors.invalidUsername" }),
+  password: z.string().min(6, { message: "pages.signup.errors.passwordLength" }),
});

export default function SignupZero() {
+  const t = useTranslations();

Committable suggestion skipped: line range outside the PR's diff.

apps/mail/app/(auth)/login/early-access/page.tsx (2)

193-204: ⚠️ Potential issue

Implement actual verification

The current verification function accepts any 6-digit code, which isn't secure for actual user validation.

Implement proper verification that validates against a server-generated code:

 function handleVerify() {
   if (verificationCode.length === 6) {
-    setVerified(true);
-    setVerificationError(false);
-    toast.success("Email verified successfully!");
-    // Redirect to /mail after verification
-    router.push("/mail");
+    // Call API to verify the code
+    fetch("/api/verify-code", {
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      body: JSON.stringify({ 
+        email: userEmail, 
+        code: verificationCode 
+      }),
+    })
+      .then((res) => {
+        if (res.ok) {
+          setVerified(true);
+          setVerificationError(false);
+          toast.success(t("pages.earlyAccess.verificationSuccess"));
+          router.push("/mail");
+        } else {
+          setVerificationError(true);
+          toast.error(t("pages.earlyAccess.invalidCode"));
+        }
+      })
+      .catch(() => {
+        toast.error(t("common.errors.serverError"));
+      });
   } else {
     setVerificationError(true);
-    toast.error("Please enter a valid 6-digit code");
+    toast.error(t("pages.earlyAccess.invalidCodeLength"));
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  function handleVerify() {
    if (verificationCode.length === 6) {
      // Call API to verify the code
      fetch("/api/verify-code", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ 
          email: userEmail, 
          code: verificationCode 
        }),
      })
        .then((res) => {
          if (res.ok) {
            setVerified(true);
            setVerificationError(false);
            toast.success(t("pages.earlyAccess.verificationSuccess"));
            router.push("/mail");
          } else {
            setVerificationError(true);
            toast.error(t("pages.earlyAccess.invalidCode"));
          }
        })
        .catch(() => {
          toast.error(t("common.errors.serverError"));
        });
    } else {
      setVerificationError(true);
      toast.error(t("pages.earlyAccess.invalidCodeLength"));
    }
  }

1-427: 🛠️ Refactor suggestion

Add internationalization support to Early Access page

This new file implements a complete early access login flow, but lacks integration with the PR's internationalization feature. All user-facing strings are hardcoded in English, which contradicts the primary goal of this PR.

To properly integrate with the i18n system, import the useTranslations hook and replace hardcoded strings with translation keys:

+"use client";
+
+import { useTranslations } from "next-intl";
 import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
 // other imports...

 function EarlyAccessContent() {
+  const t = useTranslations();
   const router = useRouter();
   // rest of component implementation...
   
   // Example of text replacement:
-  <h1 className="mb-2 text-4xl font-bold text-white">Claim your email</h1>
-  <p className="text-muted-foreground">Enter your email below to claim your account</p>
+  <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.earlyAccess.title")}</h1>
+  <p className="text-muted-foreground">{t("pages.earlyAccess.subtitle")}</p>

Don't forget to add the corresponding translation keys to the JSON files for all supported languages.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

"use client";

import { useTranslations } from "next-intl";
import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
import { useRouter, useSearchParams } from "next/navigation";
import { AnimatePresence, motion } from "framer-motion";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState, useEffect, Suspense } from "react";
import { Button } from "@/components/ui/button";
import { ArrowLeft, Check } from "lucide-react";
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import confetti from "canvas-confetti";
import { cn } from "@/lib/utils";
import Image from "next/image";
import { toast } from "sonner";
import Link from "next/link";
import { z } from "zod";

const formSchema = z.object({
  name: z.string().min(1, { message: "Name must be at least 1 character" }),
  email: z
    .string()
    .min(1, { message: "Username is required" })
    .refine((value) => !value.includes("@"), { message: "Username should not include @ symbol" }),
  earlyAccessEmail: z.string().email({ message: "Invalid early access email address" }),
  password: z.string().min(6, { message: "Password must be at least 6 characters" }),
});

// Add this component to safely use useSearchParams
function EarlyAccessContent() {
  const t = useTranslations();
  const router = useRouter();
  const searchParams = useSearchParams();

  // Get current step from URL or default to 'claim'
  const currentStep = searchParams.get("step") || "claim";

  const [showVerification, setShowVerification] = useState(
    currentStep === "verify" || currentStep === "success",
  );
  const [verified, setVerified] = useState(currentStep === "success");
  const [verificationCode, setVerificationCode] = useState("");
  const [verificationError, setVerificationError] = useState(false);
  const [userEmail, setUserEmail] = useState(searchParams.get("email") || "");

  // Update URL when step changes
  useEffect(() => {
    const params = new URLSearchParams(searchParams.toString());

    // Set the appropriate step
    if (verified) {
      params.set("step", "success");
    } else if (showVerification) {
      params.set("step", "verify");
    } else {
      params.set("step", "claim");
    }

    // Add email to URL if we have it
    if (userEmail) {
      params.set("email", userEmail);
    }

    const newUrl = `?${params.toString()}`;
    router.replace(newUrl, { scroll: false });
  }, [showVerification, verified, userEmail, router, searchParams]);

  // Trigger confetti when verified changes to true
  useEffect(() => {
    if (verified) {
      const duration = 3 * 1000;
      const animationEnd = Date.now() + duration;

      const randomInRange = (min: number, max: number) => {
        return Math.random() * (max - min) + min;
      };

      const interval = setInterval(() => {
        const timeLeft = animationEnd - Date.now();

        if (timeLeft <= 0) {
          return clearInterval(interval);
        }

        const particleCount = 50 * (timeLeft / duration);

        // since particles fall down, start a bit higher than random
        confetti({
          particleCount,
          startVelocity: 30,
          spread: 360,
          origin: {
            x: randomInRange(0.1, 0.9),
            y: randomInRange(0, 0.2),
          },
        });
      }, 250);

      return () => clearInterval(interval);
    }
  }, [verified]);

  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      email: searchParams.get("email") || "",
      password: "",
    },
  });

  function onSubmit(values: z.infer<typeof formSchema>) {
    // Append the @0.email suffix to the username
    const fullEmail = `${values.email}@0.email`;
    setUserEmail(fullEmail);

    // Use the correct sonner toast API
    toast.success("Signup successful, please verify your email");

    // Show verification screen
    setShowVerification(true);
    // URL will be updated in the useEffect
  }

  // Handle form errors with toast notifications
  const onError = (errors: any) => {
    // Define error messages mapping
    const errorMessageMap = [
      // Email errors
      {
        field: "email",
        pattern: "@ symbol",
        message: "Zero email has to have only letters (a-z), numbers (0-9), and periods (.).",
      },
      {
        field: "email",
        pattern: "required",
        message: "Username is required",
      },

      // Password errors
      {
        field: "password",
        pattern: "at least",
        message: "Password must be at least 6 characters",
      },
      {
        field: "password",
        pattern: "",
        message: "Password is required",
      },

      // Name errors
      {
        field: "name",
        pattern: "",
        message: "Name is required",
      },

      // Early access email errors
      {
        field: "earlyAccessEmail",
        pattern: "Invalid",
        message: "Invalid early access email address",
      },
      {
        field: "earlyAccessEmail",
        pattern: "",
        message: "Early access email is required",
      },
    ];

    // Find the first matching error and show toast
    for (const [field, fieldError] of Object.entries(errors)) {
      const errorMessage = (fieldError as { message?: string })?.message || "";

      // Find matching error pattern
      const matchedError = errorMessageMap.find(
        (mapping) =>
          mapping.field === field &&
          (mapping.pattern === "" || errorMessage.includes(mapping.pattern)),
      );

      if (matchedError) {
        toast.error(matchedError.message);
        return;
      }
    }

    // Fallback for any other errors
    toast.error("Please fix the form errors");
  };

  function handleVerify() {
    if (verificationCode.length === 6) {
      setVerified(true);
      setVerificationError(false);
      toast.success("Email verified successfully!");
      // Redirect to /mail after verification
      router.push("/mail");
      // URL will be updated in the useEffect
    } else {
      setVerificationError(true);
      toast.error("Please enter a valid 6-digit code");
    }
  }

  return (
    <div className="flex h-full min-h-screen w-full items-center justify-center bg-black">
      <AnimatePresence mode="wait">
        {currentStep === "claim" ? (
          // Claim screen (signup form)
          <motion.div
            key="signup"
            initial={{ opacity: 1 }}
            exit={{ opacity: 0, y: -20 }}
            className="animate-in slide-in-from-bottom-4 w-full max-w-md px-6 py-8 duration-500"
          >
            <div className="mb-4 text-center">
              <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.earlyAccess.title")}</h1>
              <p className="text-muted-foreground">{t("pages.earlyAccess.subtitle")}</p>
            </div>

            <Form {...form}>
              <form onSubmit={form.handleSubmit(onSubmit, onError)} className="mx-auto space-y-3">
                <FormField
                  control={form.control}
                  name="earlyAccessEmail"
                  render={({ field, fieldState }) => (
                    <FormItem>
                      <FormLabel className="text-muted-foreground">Early access email</FormLabel>
                      <FormControl>
                        <Input
                          placeholder="luke@example.com"
                          {...field}
                          error={!!fieldState.error}
                          className="bg-black text-sm text-white placeholder:text-sm"
                        />
                      </FormControl>
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="name"
                  render={({ field, fieldState }) => (
                    <FormItem>
                      <FormLabel className="text-muted-foreground">Name</FormLabel>
                      <FormControl>
                        <Input
                          placeholder="Luke Johnson"
                          {...field}
                          error={!!fieldState.error}
                          className="bg-black text-sm text-white placeholder:text-sm"
                        />
                      </FormControl>
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="email"
                  render={({ field, fieldState }) => (
                    <FormItem>
                      <FormLabel className="text-muted-foreground">Zero Email</FormLabel>
                      <FormControl>
                        <div className="relative w-full rounded-md">
                          <Input
                            placeholder="Username"
                            {...field}
                            onChange={(e) => {
                              // Just update the field value without showing toast errors
                              const value = e.target.value;
                              field.onChange(value);
                            }}
                            error={!!fieldState.error}
                            className="w-full bg-black pr-16 text-sm text-white placeholder:text-sm"
                          />
                          <span className="border-input absolute bottom-0 right-0 top-0 flex items-center rounded-r-md border border-l-0 px-3 py-2 text-sm text-white">
                            @0.email
                          </span>
                        </div>
                      </FormControl>
                    </FormItem>
                  )}
                />

                <FormField
                  control={form.control}
                  name="password"
                  render={({ field, fieldState }) => (
                    <FormItem>
                      <div className="flex items-center justify-between">
                        <FormLabel className="text-muted-foreground">Password</FormLabel>
                      </div>
                      <FormControl>
                        <Input
                          type="password"
                          placeholder="••••••••"
                          {...field}
                          error={!!fieldState.error}
                          className="bg-black text-white"
                        />
                      </FormControl>
                    </FormItem>
                  )}
                />

                <Button type="submit" className="w-full">
                  Create account
                </Button>
              </form>
            </Form>
          </motion.div>
        ) : (
          // Verification screen
          <motion.div
            key="verification"
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.5 }}
            className="w-full max-w-md px-6 py-8"
          >
            <div className="text-center">
              <h1 className="mb-2 text-4xl font-bold text-white">Verify your email</h1>
              <p className="text-muted-foreground">Enter the 6-digit code sent to your email</p>
            </div>

            <div className="flex flex-col items-center space-y-6">
              <InputOTP
                maxLength={6}
                value={verificationCode}
                onChange={(value) => {
                  setVerificationCode(value);
                  // Clear error when user starts typing again
                  if (verificationError) {
                    setVerificationError(false);
                  }
                }}
                className="justify-center gap-2"
              >
                <InputOTPGroup>
                  <InputOTPSlot
                    index={0}
                    className={cn(
                      "border-input h-12 w-12 bg-black text-lg text-white",
                      verificationError && "ring-2 ring-red-500 ring-offset-1",
                    )}
                  />
                  <InputOTPSlot
                    index={1}
                    className={cn(
                      "border-input h-12 w-12 bg-black text-lg text-white",
                      verificationError && "ring-2 ring-red-500 ring-offset-1",
                    )}
                  />
                  <InputOTPSlot
                    index={2}
                    className={cn(
                      "border-input h-12 w-12 bg-black text-lg text-white",
                      verificationError && "ring-2 ring-red-500 ring-offset-1",
                    )}
                  />
                  <InputOTPSlot
                    index={3}
                    className={cn(
                      "border-input h-12 w-12 bg-black text-lg text-white",
                      verificationError && "ring-2 ring-red-500 ring-offset-1",
                    )}
                  />
                  <InputOTPSlot
                    index={4}
                    className={cn(
                      "border-input h-12 w-12 bg-black text-lg text-white",
                      verificationError && "ring-2 ring-red-500 ring-offset-1",
                    )}
                  />
                  <InputOTPSlot
                    index={5}
                    className={cn(
                      "border-input h-12 w-12 bg-black text-lg text-white",
                      verificationError && "ring-2 ring-red-500 ring-offset-1",
                    )}
                  />
                </InputOTPGroup>
              </InputOTP>

              <Button
                onClick={handleVerify}
                className="w-72"
                disabled={verificationCode.length !== 6}
              >
                Verify Email
              </Button>

              <p className="text-muted-foreground text-center text-sm">
                Didn't receive a code?{" "}
                <button
                  onClick={() => toast.info("New code sent!")}
                  className="text-primary hover:underline"
                >
                  Resend
                </button>
              </p>
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}

// Main component with Suspense boundary
export default function EarlyAccess() {
  return (
    <Suspense
      fallback={
        <div className="flex h-full min-h-screen w-full items-center justify-center bg-black">
          <div className="text-white">Loading...</div>
        </div>
      }
    >
      <EarlyAccessContent />
    </Suspense>
  );
}
apps/mail/components/create/selectors/text-buttons.tsx (1)

17-26: ⚠️ Potential issue

Implement the AI chat functionality

The function currently only logs to the console without actual implementation.

Either complete the implementation or add a TODO comment with clear requirements:

 const handleChatWithAI = () => {
   // Get selected text
   const selection = editor.state.selection
   const selectedText = selection.empty 
     ? '' 
     : editor.state.doc.textBetween(selection.from, selection.to)
   
-  console.log("Chat with AI about:", selectedText)
-  // Implement chat with AI functionality
+  // TODO: Implement actual API call to AI service
+  // Requirements:
+  // 1. Send selected text to /api/ai/chat endpoint
+  // 2. Display response in a chat panel
+  // 3. Allow user to continue the conversation
+  console.log("Chat with AI about:", selectedText)
+  
+  // Temporary implementation until API is ready
+  toast.info("AI chat functionality coming soon!");
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  const handleChatWithAI = () => {
    // Get selected text
    const selection = editor.state.selection
    const selectedText = selection.empty 
      ? '' 
      : editor.state.doc.textBetween(selection.from, selection.to)
    
    // TODO: Implement actual API call to AI service
    // Requirements:
    // 1. Send selected text to /api/ai/chat endpoint
    // 2. Display response in a chat panel
    // 3. Allow user to continue the conversation
    console.log("Chat with AI about:", selectedText)
    
    // Temporary implementation until API is ready
    toast.info("AI chat functionality coming soon!");
  }
apps/mail/app/(auth)/login/login-client.tsx (1)

1-333: 🛠️ Refactor suggestion

Add internationalization support to LoginClient

This new component lacks integration with the internationalization feature that is the focus of this PR. All user-facing strings are hardcoded in English.

Import and use the useTranslations hook:

 "use client";

+import { useTranslations } from "next-intl";
 import { GitHub, Google } from "@/components/icons/icons";
 import { signIn, useSession } from "@/lib/auth-client";
 // other imports...

 export function LoginClient({ providers, isProd }: LoginClientProps) {
+  const t = useTranslations();
   const router = useRouter();
   // rest of component...
   
   // Example replacements:
-  <p className="text-center font-bold text-4xl md:text-5xl">Login to Zero</p>
+  <p className="text-center font-bold text-4xl md:text-5xl">{t("pages.login.title")}</p>

-  <h3 className="ml-2 text-base font-medium text-black dark:text-white">
-    Configuration Required
-  </h3>
+  <h3 className="ml-2 text-base font-medium text-black dark:text-white">
+    {t("pages.login.configRequired")}
+  </h3>

-  <Button>Continue with {provider.name}</Button>
+  <Button>{t("pages.login.continueWith", { provider: provider.name })}</Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

"use client";

import { useTranslations } from "next-intl";
import { GitHub, Google } from "@/components/icons/icons";
import { signIn, useSession } from "@/lib/auth-client";
import { Button } from "@/components/ui/button";
import { useRouter } from "next/navigation";
import { useEffect, ReactNode, useState } from "react";
import Image from "next/image";
import { toast } from "sonner";
import { EnvVarInfo } from "@/lib/auth-providers";
import Link from "next/link";

interface EnvVarStatus {
  name: string;
  set: boolean;
  source: string;
  defaultValue?: string;
}

interface Provider {
  id: string;
  name: string;
  enabled: boolean;
  required?: boolean;
  envVarInfo?: EnvVarInfo[];
  envVarStatus: EnvVarStatus[];
  isCustom?: boolean;
  customRedirectPath?: string;
}

interface LoginClientProps {
  providers: Provider[];
  isProd: boolean;
}

const getProviderIcon = (providerId: string, className?: string): ReactNode => {
  const defaultClass = className || "w-5 h-5 mr-2";

  switch (providerId) {
    case "google":
      return <Google className={defaultClass} />;
    case "github":
      return <GitHub className={defaultClass} />;
    case "zero":
      return (
        <Image
          src="/white-icon.svg"
          alt="Zero"
          width={15}
          height={15}
          className="invert dark:invert-0 mr-2"
        />
      );
    default:
      return null;
  }
};

export function LoginClient({ providers, isProd }: LoginClientProps) {
  const t = useTranslations();
  const router = useRouter();
  const { data: session, isPending } = useSession();
  const [expandedProviders, setExpandedProviders] = useState<Record<string, boolean>>({});

  useEffect(() => {
    const missingProviders = providers.filter(p => p.required && !p.enabled);
    if (missingProviders.length > 0 && missingProviders[0]?.id) {
      setExpandedProviders({ [missingProviders[0].id]: true });
    }
  }, [providers]);

  const missingRequiredProviders = providers
    .filter(p => p.required && !p.enabled)
    .map(p => p.name);

  const missingProviders = providers
    .filter(p => p.required && !p.enabled && p.envVarInfo)
    .map(p => ({
      id: p.id,
      name: p.name,
      envVarInfo: p.envVarInfo || [],
      envVarStatus: p.envVarStatus
    }));

  const toggleProvider = (providerId: string) => {
    setExpandedProviders(prev => ({
      ...prev,
      [providerId]: !prev[providerId]
    }));
  };

  useEffect(() => {
    if (!isPending && session?.connectionId) {
      router.push("/mail");
    }
  }, [session, isPending, router]);

  if (isPending || (session && session.connectionId)) return null;

  const displayProviders = isProd ? providers.filter(p => p.enabled || p.isCustom) : providers;
  const hasMissingRequiredProviders = missingRequiredProviders.length > 0;
  const shouldShowDetailedConfig = !isProd && hasMissingRequiredProviders;
  const shouldShowSimplifiedMessage = isProd && hasMissingRequiredProviders;

  const handleProviderClick = (provider: Provider) => {
    if (provider.isCustom && provider.customRedirectPath) {
      router.push(provider.customRedirectPath);
    } else {
      toast.promise(
        signIn.social({
          provider: provider.id as any,
          callbackURL: "/mail",
        }),
        {
          loading: "Redirecting...",
          success: "Redirected successfully!",
          error: "Login redirect failed",
        }
      );
    }
  };

  const sortedProviders = [...displayProviders].sort((a, b) => {
    if (a.id === "zero") return -1;
    if (b.id === "zero") return 1;

    if (a.required && !b.required) return -1;
    if (!a.required && b.required) return 1;
    return 0;
  });

  return (
    <div className="flex flex-col min-h-screen w-full items-center justify-between">
      <div className="absolute top-4 left-4 z-10">
        <Link href="/">
          <Image
            src="/white-icon.svg"
            alt="Zero"
            width={40}
            height={40}
            className="cursor-pointer invert dark:invert-0"
          />
        </Link>
      </div>
      
      <div className="max-w-[600px] mx-auto space-y-8 px-4 duration-500 animate-in slide-in-from-bottom-4 sm:px-12 md:px-0 flex-grow flex items-center justify-center">
        <div className="space-y-8 w-full">
          <p className="text-center font-bold text-4xl md:text-5xl">{t("pages.login.title")}</p>

          {shouldShowDetailedConfig && (
            <div className="bg-black/5 border border-black/10 rounded-lg p-5 dark:bg-white/5 dark:border-white/10">
              <div className="flex flex-col space-y-4">
                <div className="flex items-center">
                  <svg
                    className="h-5 w-5 text-black dark:text-white"
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke="currentColor"
                    strokeWidth={2}
                  >
                    <path
                      strokeLinecap="round"
                      strokeLinejoin="round"
                      d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
                    />
                  </svg>
                  <h3 className="ml-2 text-base font-medium text-black dark:text-white">
                    {t("pages.login.configRequired")}
                  </h3>
                </div>

                <p className="text-sm text-black/80 dark:text-white/80">
                  To enable login with <span className="font-semibold">{missingRequiredProviders.join(', ')}</span>, 
                  add these variables to your <code className="font-mono bg-black/10 dark:bg-white/10 px-1.5 py-0.5 rounded">.env</code> file:
                </p>

                <div className="space-y-3">
                  {missingProviders.map((provider) => (
                    <div key={provider.id} className="border border-black/10 dark:border-white/10 rounded-md overflow-hidden">
                      <button
                        onClick={() => toggleProvider(provider.id)}
                        className="flex w-full items-center justify-between px-4 py-3 text-left bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 transition-colors"
                      >
                        <div className="flex items-center">
                          {getProviderIcon(provider.id)}
                          <span className="font-medium text-black dark:text-white">{provider.name}</span>
                        </div>
                        <div className="flex items-center">
                          <span className="text-xs mr-2 text-black/60 dark:text-white/60">
                            {provider.envVarStatus.filter(v => !v.set).length} missing
                          </span>
                          <svg
                            className={`h-5 w-5 text-black/60 dark:text-white/60 transition-transform duration-200 ${expandedProviders[provider.id] ? 'rotate-180' : ''}`}
                            xmlns="http://www.w3.org/2000/svg"
                            viewBox="0 0 24 24"
                            fill="none"
                            stroke="currentColor"
                            strokeWidth="2"
                            strokeLinecap="round"
                            strokeLinejoin="round"
                          >
                            <polyline points="6 9 12 15 18 9"></polyline>
                          </svg>
                        </div>
                      </button>

                      <div 
                        className={`overflow-hidden transition-all duration-300 ease-in-out ${expandedProviders[provider.id] ? 'max-h-[500px] opacity-100' : 'max-h-0 opacity-0'}`}
                      >
                        <div className="p-4 font-mono text-sm bg-black/[0.03] dark:bg-white/[0.03]">
                          {provider.envVarStatus.map((envVar) => (
                            <div key={envVar.name} 
                              className={`flex items-start mb-3 last:mb-0 transition-all duration-300 ease-in-out ${expandedProviders[provider.id] ? 'translate-y-0 opacity-100' : 'translate-y-2 opacity-0'}`}
                              style={{ 
                                transitionDelay: expandedProviders[provider.id] 
                                  ? `${provider.envVarStatus.indexOf(envVar) * 50}ms` 
                                  : '0ms' 
                              }}
                            >
                              <div className={`w-2 h-2 mt-1.5 mr-2 rounded-full ${!envVar.set ? 'bg-red-500' : 'bg-green-500'}`} />
                              <div className="flex-1">
                                <div className="flex items-center">
                                  <span className="text-black dark:text-white font-semibold">{envVar.name}</span>
                                  <span className={`ml-2 text-xs px-1.5 py-0.5 rounded ${!envVar.set ? 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300' : 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'}`}>
                                    {!envVar.set ? 'Missing' : 'Set'}
                                  </span>
                                </div>
                                {!envVar.set && (
                                  <div className="mt-1.5">
                                    <code className="block text-black/80 dark:text-white/80">
                                      {envVar.name}={envVar.defaultValue || `# from ${envVar.source}`}
                                    </code>
                                  </div>
                                )}
                              </div>
                            </div>
                          ))}
                        </div>
                      </div>
                    </div>
                  ))}
                </div>

                <a
                  href="https://github.com/Mail-0/Mail-0/blob/main/README.md"
                  target="_blank"
                  rel="noopener noreferrer"
                  className="text-sm inline-flex items-center text-black/60 hover:text-black dark:text-white/60 dark:hover:text-white underline underline-offset-2"
                >
                  Setup instructions in documentation
                  <svg 
                    className="ml-1 h-3 w-3" 
                    xmlns="http://www.w3.org/2000/svg" 
                    fill="none" 
                    viewBox="0 0 24 24" 
                    stroke="currentColor"
                  >
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
                  </svg>
                </a>
              </div>
            </div>
          )}

          {shouldShowSimplifiedMessage && (
            <div className="bg-black/5 border border-black/10 rounded-lg p-4 dark:bg-white/5 dark:border-white/10">
              <div className="flex items-center">
                <svg
                  className="h-5 w-5 text-black dark:text-white"
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke="currentColor"
                  strokeWidth={2}
                >
                  <path
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
                  />
                </svg>
                <p className="ml-2 text-sm text-black/80 dark:text-white/80">
                  Authentication service unavailable
                </p>
              </div>
            </div>
          )}

          {!hasMissingRequiredProviders && (
            <div className="relative z-10 mx-auto flex w-full flex-col items-center justify-center gap-2">
              {sortedProviders.map(provider => 
                (provider.enabled || provider.isCustom) && (
                  <Button
                    key={provider.id}
                    onClick={() => handleProviderClick(provider)}
                    className="border-input bg-background text-primary hover:bg-accent hover:text-accent-foreground h-12 w-full rounded-lg border-2 bg-black"
                  >
                    {getProviderIcon(provider.id)}
                    {t("pages.login.continueWith", { provider: provider.name })}
                  </Button>
                )
              )}
            </div>
          )}
        </div>
      </div>
      
      <footer className="w-full py-4 px-6">
        <div className="max-w-6xl mx-auto flex justify-center items-center gap-6">
          <a 
            href="/terms" 
            className="text-[10px] text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
          >
            Terms of Service
          </a>
          <a 
            href="/privacy" 
            className="text-[10px] text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
          >
            Privacy Policy
          </a>
        </div>
      </footer>
    </div>
  );
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apps/mail/components/create/editor.tsx (1)

115-129: ⚠️ Potential issue

Unrestricted link protocol could be exploited.
Currently, a user can enter potentially dangerous protocols (e.g., "javascript:") that won't be sanitized.

Consider disallowing scripts or other malicious protocols, for example:

- if (formattedUrl && !/^https?:\/\//i.test(formattedUrl)) {
-   formattedUrl = `https://${formattedUrl}`;
- }
+ if (formattedUrl) {
+   // Drop any suspicious protocols
+   if (/^javascript:/i.test(formattedUrl)) {
+     editor.chain().focus().unsetLink().run();
+     setLinkDialogOpen(false);
+     return;
+   }
+   // Auto-add https if missing
+   if (!/^https?:\/\//i.test(formattedUrl)) {
+     formattedUrl = `https://${formattedUrl}`;
+   }
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  const handleSaveLink = () => {
    // empty
    if (linkUrl === "") {
      editor.chain().focus().unsetLink().run();
    } else {
      // Format the URL with proper protocol if missing
      let formattedUrl = linkUrl;
      if (formattedUrl) {
        // Drop any suspicious protocols
        if (/^javascript:/i.test(formattedUrl)) {
          editor.chain().focus().unsetLink().run();
          setLinkDialogOpen(false);
          return;
        }
        // Auto-add https if missing
        if (!/^https?:\/\//i.test(formattedUrl)) {
          formattedUrl = `https://${formattedUrl}`;
        }
      }
      // set link
      editor.chain().focus().setLink({ href: formattedUrl }).run();
    }
    setLinkDialogOpen(false);
  };

@nyzss nyzss changed the title feat(i18n): initialize internationalization feat(i18n): internationalization of mail app Mar 11, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
apps/mail/components/create/create-email.tsx (1)

157-175: ⚠️ Potential issue

Remove duplicated effect.

There's a duplicated useEffect block with identical functionality. This is unrelated to the internationalization changes but should be fixed.

  React.useEffect(() => {
    if (!hasUnsavedChanges) return;

    const autoSaveTimer = setTimeout(() => {
      saveDraft();
    }, 3000);

    return () => clearTimeout(autoSaveTimer);
  }, [hasUnsavedChanges, saveDraft]);

-  React.useEffect(() => {
-    if (!hasUnsavedChanges) return;
-
-    const autoSaveTimer = setTimeout(() => {
-      saveDraft();
-    }, 3000);
-
-    return () => clearTimeout(autoSaveTimer);
-  }, [hasUnsavedChanges, saveDraft]);
🧹 Nitpick comments (4)
apps/mail/components/ui/nav-main.tsx (1)

217-217: Consider using nullish coalescing operator instead of logical OR.

While adding a fallback value of "0" for undefined counts is a good practice, consider using the nullish coalescing operator (??) instead of logical OR (||).

The || operator will use the fallback for any falsy value (including 0), while ?? only uses the fallback for null or undefined. This ensures that legitimate zero counts are displayed as "0" from the actual data.

-            ?.count?.toLocaleString() || "0"}
+            ?.count?.toLocaleString() ?? "0"}
apps/mail/components/create/create-email.tsx (3)

112-112: Consider translating error messages.

There are several untranslated error messages in the component that should be internationalized for consistency.

-      toast.error(`Invalid email format: ${trimmedEmail}`);
+      toast.error(t("errors.invalidEmailFormat", { email: trimmedEmail }));

You should also consider translating other error and success messages at lines 59, 85, 151, 183, 188, 193, 205, and 214.


458-464: Internationalize draft save status messages.

The draft save status messages ("Saving...", "Save draft", "Draft saved") should also be translated for consistency.

  {isSaving ? (
    <>
-     <span className="animate-pulse">Saving...</span>
+     <span className="animate-pulse">{t("common.drafts.saving")}</span>
    </>
  ) : hasUnsavedChanges ? (
-   <>Save draft</>
+   <>{t("common.drafts.saveDraft")}</>
  ) : (
-   <>Draft saved</>
+   <>{t("common.drafts.draftSaved")}</>
  )}

385-386: Consider optimizing repeated translations.

The same translation key is used multiple times (here and at lines 373-374). Consider storing translated strings in variables when they're used multiple times to avoid redundant function calls.

+ const fileCountText = t("common.replyCompose.fileCount", { count: attachments.length });
+ const attachmentsText = t("pages.createEmail.attachments");

// Then use fileCountText and attachmentsText variables where needed
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 439084c and fb78245.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/mail/components/create/create-email.tsx (10 hunks)
  • apps/mail/components/ui/app-sidebar.tsx (3 hunks)
  • apps/mail/components/ui/nav-main.tsx (3 hunks)
  • apps/mail/package.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/mail/components/ui/app-sidebar.tsx
  • apps/mail/package.json
🧰 Additional context used
🪛 Biome (1.9.4)
apps/mail/components/ui/nav-main.tsx

[error] 211-211: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (10)
apps/mail/components/ui/nav-main.tsx (5)

10-11: Good addition of internationalization imports.

The imports for useTranslations and MessageKey are correctly added to support the i18n integration.


179-180: Correctly implemented translation hook.

The useTranslations hook is properly set up to enable internationalization across the component.


188-188: Good implementation of text translation.

The component now correctly uses the translation function to display internationalized text.


210-210: Good implementation of text translation.

The component now correctly uses the translation function to display internationalized text.


211-217: Fix potential null reference issue with optional chaining.

This is a duplicate of a previously identified issue. The static analysis tool correctly suggests improving the optional chaining pattern for better code safety.

🧰 Tools
🪛 Biome (1.9.4)

[error] 211-211: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apps/mail/components/create/create-email.tsx (5)

14-14: Good use of next-intl for internationalization.

Adding the useTranslations hook import is the first step toward internationalizing the component.


94-95: LGTM - Proper hook initialization.

Good practice to initialize the translation hook at the top level of your component.


268-268: Correctly replaced hardcoded string with translation key.

This implementation properly uses the translation function for the file drop message.


309-309: Properly conditionally rendered placeholder text.

Good implementation that maintains the conditional logic while internationalizing the placeholder.


373-374: Good implementation of pluralization.

The use of the count parameter in the translation is the correct approach for handling pluralized content.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/mail/components/mail/mail-iframe.tsx (1)

61-61: Consider translating the iframe title attribute

While you've correctly internationalized the loading message, consider also translating the "Email Content" title attribute on the iframe for complete internationalization support.

-        title="Email Content"
+        title={t("common.mailDisplay.emailContentTitle")}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb78245 and bfc9f07.

📒 Files selected for processing (8)
  • apps/mail/app/(error)/not-found.tsx (1 hunks)
  • apps/mail/app/(routes)/settings/[...settings]/page.tsx (2 hunks)
  • apps/mail/components/create/create-email.tsx (12 hunks)
  • apps/mail/components/mail/mail-iframe.tsx (3 hunks)
  • apps/mail/components/mail/reply-composer.tsx (12 hunks)
  • apps/mail/messages/en.json (1 hunks)
  • apps/mail/messages/fr.json (1 hunks)
  • apps/mail/messages/tr.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/mail/components/mail/reply-composer.tsx
  • apps/mail/components/create/create-email.tsx
🔇 Additional comments (31)
apps/mail/app/(routes)/settings/[...settings]/page.tsx (3)

9-9: Well implemented i18n import.

The import for useTranslations from the next-intl library is correctly added to support the internationalization requirements.


24-24: Properly initialized translation hook.

The translation hook is correctly initialized and stored in the t constant, following best practices for using the next-intl library.


29-29: Good implementation of internationalized error message.

The hardcoded error message has been properly replaced with a translation key using the hierarchical structure (pages.error.settingsNotFound), which is a good practice for organizing translations.

apps/mail/app/(error)/not-found.tsx (2)

6-6: Successful implementation of internationalization!

The introduction of the useTranslations hook and replacement of hardcoded text with translation keys is well implemented. The translation keys follow a logical hierarchical structure (pages.error.notFound.title/description/goBack), making them easy to understand and maintain.

Also applies to: 10-10, 25-27, 38-38


13-14: Clean CSS class reorganization

The reordering of CSS classes (putting Tailwind utility classes after component classes) improves readability while maintaining the same styling functionality.

Also applies to: 16-16, 18-18, 35-35

apps/mail/components/mail/mail-iframe.tsx (3)

3-3: LGTM! Import for internationalization support

This import correctly adds the necessary hook for internationalization support from the next-intl library.


16-16: LGTM! Translation hook initialization

Properly initialized the translation hook that will be used to translate UI text.


50-50: LGTM! Proper internationalization implementation

Good replacement of the hardcoded string with a translation key following a logical namespace hierarchy (common.mailDisplay.loadingMailContent).

apps/mail/messages/fr.json (10)

1-16: Overall Structure and Formatting – French Translations
The file is well organized, with clear categorization (common, themes, commandPalette, etc.) and correct JSON syntax. The translations for basic UI elements (e.g., actions, themes) appear accurate and consistent.


17-45: Command Palette Section – French Translations
The keys and values under the commandPalette section are clear and linguistically sound. They properly localize quick navigation and action items.


46-65: Search Bar Section – French Translations
The translations for the search bar entries (e.g., “pickDateRange”, “search”, “advancedSearch”) are appropriate and consistent with French UI terminology.


66-72: Navigation User Section – French Translations
The values for “navUser” (like “Support client” and “Se connecter”) are correctly translated and contextually appropriate.


73-79: Mail Categories and Reply/Compose Sections
Both the mailCategories and replyCompose sections use clear and context-specific language. The pluralization strings in “attachmentCount” and “fileCount” are properly formatted.


80-113: Mail Display and Thread Display Sections
The translations in these sections (e.g., “Chargement du contenu de l'email…”, “Quitter le mode plein écran”) are well done. No issues identified in formatting or terminology.


115-124: Navigation Sidebar Section
The sidebar keys are correctly mapped (e.g., “Boîte de réception”, “Brouillons”) and maintain consistency with the common nomenclature used in French interfaces.


126-142: Settings and Error Pages Sections
The “settings” and “pages” sections provide clear, descriptive text for both settings and error messages. The keys and their French values are correctly structured.


210-216: Create Email Section – French Translations
The translations for composing an email (e.g., “Corps”, “Déposez des fichiers pour les joindre”) are clear and contextually correct.


143-209: ⚠️ Potential issue

Settings Subsections – Detailed Translation Review
All subsections (general, connections, security, appearance, shortcuts) are detailed and comprehensive.

However, in the shortcuts section (lines approximately 186–207), the translation for the key "inbox" is given as "Emails envoyés", which is identical to "sentMail". Typically, the "inbox" key should represent the user's inbox (e.g., "Boîte de réception").

Suggested Diff:

- "inbox": "Emails envoyés",
+ "inbox": "Boîte de réception",

Please verify if this correction aligns with the intended UI context.

Likely an incorrect or invalid review comment.

apps/mail/messages/en.json (5)

1-16: Overall Structure and Formatting – English Translations
The English localization file is comprehensive and well structured, with clear sections covering common actions, themes, command palette, search bar, and navigation.


17-45: Command Palette and Actions – English Translations
All keys in the command palette (titles, descriptions, placeholders, commands) are correctly written in English with appropriate clarity and consistency.


46-65: Search Bar and Nav User Sections
The search bar and navUser translations (e.g., “Clear search”, “Customer Support”) are correctly rendered. They maintain clarity in UI communication.


66-114: Remaining Sections (Mail Categories, ReplyCompose, MailDisplay, ThreadDisplay, Sidebar)
All sections provide clear, unambiguous text strings. The hierarchical organization and key naming conventions are consistent with UI expectations.


115-221: Settings, Pages, and Create Email Sections
The translations for settings (general preferences, connections, security, appearance, shortcuts) and email creation workflows are detailed and correctly localized. The keys such as "inbox", "sentMail", and "drafts" correctly use distinct values (e.g., "inbox": "Inbox" vs. "sentMail": "Sent Mail").

apps/mail/messages/tr.json (8)

1-16: Overall Structure and Formatting – Turkish Translations
The Turkish translation file mirrors the structure of the other locale files and is properly segmented (common, themes, commandPalette, etc.). The file format and JSON syntax are correct.


17-44: Command Palette and Search Bar – Turkish Translations
The commandPalette section is consistent with the localization in other languages. However, in the searchBar section, please note the upcoming comment regarding the “clearSearch” key.


50-71: Remaining Common, NavUser, and Mail Categories Sections
The translations for keys such as “search,” “recipient,” “sender,” and the navUser section (e.g., “Müşteri Desteği”, “Giriş Yap”) are contextually accurate and well phrased.


72-113: Reply/Compose, Mail Display, and Thread Display Sections – Turkish Translations
These sections maintain consistency with clear, user-friendly language (e.g., “Yanıtla”, “E-posta içeriği yükleniyor…”).


115-132: Navigation Sidebar and Settings Sections – Turkish Translations
The translations under the navigation-sidebar and settings overview (e.g., “Posta Kutusu”, “Taslaklar”, “Genel”, “Bağlantılar”) are correctly localized.


133-141: Error Pages Section – Turkish Translations
The error messages and settings-not-found strings, such as “Sayfa Bulunamadı” and “404 - Ayarlar sayfası bulunamadı”, are clear and correctly communicated.


142-209: Detailed Settings Subsections – Turkish Translations
The settings subsections (general, connections, security, appearance, shortcuts) are detailed and well translated. The “shortcuts” section, in particular, uses distinct translations for keys like "inbox": "Gelen Kutusu" and "sentMail": "Gönderilen Postalar", which is correct.


210-216: Create Email Section – Turkish Translations
The final section related to email creation (e.g., “İçerik”, “Mesajınızı buraya yazın...”) is accurate and maintains consistency with the other locale files.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@MrgSub MrgSub assigned MrgSub and unassigned MrgSub Mar 13, 2025
@MrgSub MrgSub added enhancement New feature or request High Priority High Priority Work labels Mar 13, 2025
Copy link
Contributor

@ahmetskilinc ahmetskilinc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implementation and Turkish language look good to me

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/mail/components/mail/mail.tsx (1)

56-56: Consider using optional chaining for safer property access.

The static analysis hints suggest using optional chaining for accessing the tags property. This would prevent potential errors if an item doesn't have a tags property.

-const filtered = items.filter((item) => item.tags && item.tags.includes(filterTag));
+const filtered = items.filter((item) => item.tags?.includes(filterTag));
🧰 Tools
🪛 Biome (1.9.4)

[error] 56-56: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apps/mail/components/mail/thread-display.tsx (1)

274-277: Consider consolidating class names

While the button styling works, there's an opportunity to use the cn() utility to organize and consolidate the class names for better maintainability.

-                  <Button
-                    variant="ghost"
-                    className="h-8 w-8 p-0 md:h-fit md:w-auto md:px-2"
+                  <Button
+                    variant="ghost"
+                    className={cn("h-8 w-8 p-0 md:h-fit md:w-auto md:px-2")}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b5022c and f10e0c3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • apps/mail/components/connection/add.tsx (1 hunks)
  • apps/mail/components/create/create-email.tsx (12 hunks)
  • apps/mail/components/mail/mail-display.tsx (5 hunks)
  • apps/mail/components/mail/mail.tsx (15 hunks)
  • apps/mail/components/mail/reply-composer.tsx (13 hunks)
  • apps/mail/components/mail/thread-display.tsx (6 hunks)
  • apps/mail/components/ui/app-sidebar.tsx (3 hunks)
  • apps/mail/components/ui/nav-main.tsx (3 hunks)
  • apps/mail/messages/en.json (1 hunks)
  • apps/mail/messages/fr.json (1 hunks)
  • apps/mail/messages/tr.json (1 hunks)
  • apps/mail/package.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/mail/messages/tr.json
  • apps/mail/components/ui/app-sidebar.tsx
  • apps/mail/package.json
  • apps/mail/messages/fr.json
  • apps/mail/components/mail/mail-display.tsx
  • apps/mail/components/mail/reply-composer.tsx
  • apps/mail/components/create/create-email.tsx
  • apps/mail/components/connection/add.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
apps/mail/components/mail/mail.tsx

[error] 56-56: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apps/mail/components/ui/nav-main.tsx

[error] 211-211: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (24)
apps/mail/components/mail/mail.tsx (9)

25-25: Good integration of the internationalization library.

Adding the useTranslations hook from 'next-intl' is a key change that enables internationalization support throughout the component.


49-54: Style refactoring of category map keys.

The quotes around object keys have been removed, which is a valid JavaScript syntax when keys are valid identifiers. This change is stylistic and doesn't affect functionality.


400-433: Good implementation of i18n for mail categories.

The hardcoded category names have been replaced with translation keys, which is essential for internationalization. These keys will now be used to retrieve localized strings from the translation files.


436-446: Improved type definitions in function signature.

The parameter types have been formatted with semicolons instead of commas, providing better clarity in the TypeScript interface. This follows TypeScript best practices.


448-449: Added translation hook for i18n support.

The useTranslations hook is properly initialized, allowing the component to access localized strings through the t function.


551-553: Correctly implemented translation of category names.

The category names are now properly translated using the t function and the appropriate message keys, replacing hardcoded strings with localized content.


558-558: Consistent translation in tooltip content.

The tooltip content is also properly internationalized, ensuring a consistent user experience across the entire component.


177-182: Improved code formatting for the useThreads hook call.

The parameter formatting has been improved for better readability, which aligns with general code style best practices.


295-295: Simplified conditional rendering in MailCategoryTabs.

The double negation operator (!!) is used to convert a nullable value to a boolean, which is a concise way to handle the conditional rendering of the icons.

apps/mail/components/ui/nav-main.tsx (5)

10-11: Good implementation of i18n imports

The imports for MessageKeys, useTranslations from next-intl and MessageKey from the navigation config are appropriate for implementing internationalization support as outlined in the PR objectives.


179-180: Well-placed translation hook initialization

The useTranslations hook is correctly initialized inside the NavItem component to make translation functions available where they're needed.


188-188: Properly implemented translation

The text rendering change correctly implements translations by using the translation function with proper type casting, resolving the previously identified issue with an extra "s" character.


210-210: Properly implemented translation

The text rendering change correctly implements translations by using the translation function with proper type casting.


217-217: Good fallback implementation for badge count

Adding the fallback value of "0" for undefined counts is a good practice to ensure that a meaningful value is always displayed, addressing the previous review suggestion.

apps/mail/messages/en.json (3)

1-222: Well-structured translations file!

This is a comprehensive and well-organized English localization file. The hierarchical structure makes it easy to locate translations, and I appreciate the proper use of pluralization in the attachment and file count entries.


86-87: Good implementation of pluralization

The pluralization format for attachments and files is well implemented, properly handling zero, singular, and plural cases.


1-222:

Details

❓ Verification inconclusive

Verify all UI strings are covered

While this translation file appears comprehensive, it's important to ensure all user-facing text in the application is covered.


🏁 Script executed:

#!/bin/bash
# Look for potential hardcoded strings in components that might need internationalization

# Check for common UI text patterns in TSX/JSX files
echo "Searching for potential hardcoded strings in component files..."
rg --type tsx --type jsx -C 1 "(\"|')\w{3,50}(\"|')" -g "apps/mail/**" | grep -v "t("

# Check specifically for common UI attributes that often contain user-facing text
echo "Checking for potentially untranslated UI attributes..."
rg --type tsx --type jsx -C 1 "(title|placeholder|aria-label|label)=(\"|')\w{3,}(\"|')" -g "apps/mail/**" | grep -v "t("

Length of output: 530


Below is an updated shell script that searches for potential hardcoded UI text in the Mail app’s JSX/TSX files without relying on ripgrep’s built‐in file type definitions. The previous script produced “unrecognized file type: tsx” errors, so this version uses explicit glob patterns to target *.tsx and *.jsx files. Please run this script and manually verify that all user-facing text is being translated (i.e. wrapped in t() or similar) so that nothing is left hardcoded.

Next Steps:

  • Run the updated script to see if any untranslated user-facing text remains.
  • Manually verify that all UI text in components is either translated or intentionally excluded.

Once confirmed, you can be confident that the translation file in apps/mail/messages/en.json is comprehensive.


🏁 Script executed:

#!/bin/bash
# Verify presence of TSX and JSX files in the Mail app
echo "Listing *.tsx files in apps/mail..."
fd -e tsx apps/mail
echo "Listing *.jsx files in apps/mail..."
fd -e jsx apps/mail

# Search for potential hardcoded strings by looking for string literals,
# filtering out those that appear to be wrapped in a translation function ("t(").
echo "Searching for potential hardcoded UI strings in TSX/JSX files..."
rg -C 1 "(\"|')\w{3,50}(\"|')" apps/mail --glob "*.tsx" --glob "*.jsx" | grep -v "t("

# Specifically check UI attributes that often contain user-facing text
echo "Checking for untranslated UI attributes (title, placeholder, aria-label, label)..."
rg -C 1 "(title|placeholder|aria-label|label)=(\"|')\w{3,}(\"|')" apps/mail --glob "*.tsx" --glob "*.jsx" | grep -v "t("

Length of output: 66870


Action: Verify Comprehensive Internationalization of UI Text

The translation file in apps/mail/messages/en.json appears to cover a wide range of UI strings. An automated scan for hardcoded strings in the JSX/TSX components—using glob patterns to bypass file-type issues—did not flag any obvious untranslated messages that are missing a translation wrapper (e.g., t("…")). However, the output contained many matches from various parts of the code, many of which are not necessarily user-facing text (e.g., import statements, configuration strings, etc.), which makes purely automated inference inconclusive.

Please verify manually:

  • User-Facing Text: Ensure that any literal string displayed to the user in components is properly localized via your translation function.
  • Component Review: Confirm that components with UI labels, placeholders, and other text (e.g., titles, aria-labels) are either using the translation keys provided in the JSON file or intentionally left unlocalized.
  • Ongoing Sync: As the UI evolves, ensure that new text strings are added to the translation file if they are meant for end users.

Based on our initial checks, no glaring issues were found, but a final manual verification is recommended to ensure full coverage.

apps/mail/components/mail/thread-display.tsx (7)

21-26: Good integration of internationalization

The added import for useTranslations from next-intl properly sets up this component for internationalization.


199-200: Proper hook usage for translations

The implementation of the useTranslations hook is correct and follows best practices for NextJS internationalization.


242-246: Successfully replaced hardcoded strings with translations

You've correctly replaced the hardcoded "Close" label with the translation key, maintaining the same functionality while enabling internationalization.


251-255: Good conditional translation implementation

The conditional rendering for fullscreen/exit fullscreen text is properly internationalized while maintaining the existing logic.


280-281: Successfully internationalized aria/screen reader text

Great attention to detail by ensuring the screen reader text is also properly internationalized.


284-296: Complete dropdown menu internationalization

All dropdown menu items have been properly internationalized with appropriate translation keys.


337-343: Consistent internationalization patterns

You've maintained consistent patterns for internationalization throughout the component, including in conditional rendering scenarios.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/mail/components/ui/nav-main.tsx (1)

211-218: Fixed potential null reference issue with fallback value.

Good implementation of the fallback value for count using the logical OR operator, which addresses the null reference issue mentioned in the previous review.

However, the static analysis tool is suggesting an improvement to the way we're chaining operations:

  {stats && stats.find((stat) => stat.label?.toLowerCase() === item.title?.toLowerCase()) && (
    <Badge className="ml-auto rounded-md" variant="outline">
      {stats
-       .find((stat) => stat.label?.toLowerCase() === item.title?.toLowerCase())
+       ?.find((stat) => stat.label?.toLowerCase() === item.title?.toLowerCase())
        ?.count?.toLocaleString() || "0"}
    </Badge>
  )}

While your code already checks if stats exists in the outer condition, adding the optional chaining before find would follow best practices for optional chaining throughout the entire expression.

🧰 Tools
🪛 Biome (1.9.4)

[error] 211-211: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f10e0c3 and a734fd4.

📒 Files selected for processing (2)
  • apps/mail/app/(routes)/settings/connections/page.tsx (5 hunks)
  • apps/mail/components/ui/nav-main.tsx (3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
apps/mail/components/ui/nav-main.tsx

[error] 211-211: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (14)
apps/mail/components/ui/nav-main.tsx (4)

10-11: Great internationalization setup!

Nicely added imports for the internationalization functionality with next-intl. The MessageKey type import ensures proper type-checking for translation keys.


179-180: LGTM!

Clean implementation of the translation hook.


188-188: Fixed translation string implementation.

Good job removing the trailing "s" character that was previously flagged in the earlier review. The translation function is now properly implemented.


210-210: Translation properly implemented.

The title is correctly passed to the translation function with the appropriate type casting.

apps/mail/app/(routes)/settings/connections/page.tsx (10)

14-14: Good implementation of i18n imports

The addition of useTranslations from next-intl and maintaining existing component imports aligns well with the internationalization objective of this PR.

Also applies to: 21-21


31-31: Correct usage of the translation hook

The translation hook is properly initialized and stored in the t variable, following next-intl best practices.


36-36: User feedback is now properly internationalized

Toast messages now use translation keys instead of hardcoded strings, which is essential for a good multilingual user experience.

Also applies to: 41-41


47-50: Settings card content properly internationalized

The SettingsCard component now uses translated title and description, ensuring consistent internationalization across the UI.


53-53: Improved layout styling

The layout adjustments for various elements (loading skeletons, connection items) improve the UI consistency and spacing.

Also applies to: 57-57, 61-61, 66-66, 71-71, 75-75, 131-131


138-141: Dialog content properly internationalized

The dialog title and description now use translation keys instead of hardcoded strings, providing a consistent multilingual experience during important user actions.


145-147: Dialog buttons text properly internationalized

Button labels in the dialog are now internationalized, ensuring consistency across the UI for all interactive elements.

Also applies to: 150-152


164-165: Improved button styling and internationalized label

The "Add Email" button has been updated with proper variant styling and its label is now internationalized, maintaining UI consistency while supporting multiple languages.

Also applies to: 170-170


89-89:

Details

✅ Verification successful

Verify non-null assertion on emailProviders

The code uses a non-null assertion (!.icon) which assumes emailProviders[0] always exists. Consider adding a fallback for safety.


🏁 Script executed:

#!/bin/bash
# Check if emailProviders array is properly initialized and always has at least one element
rg -A 5 "emailProviders\s*=\s*\[" --type ts

Length of output: 623


Validated Non-null Assertion in Email Providers Usage
The verification confirms that the emailProviders array is defined in apps/mail/constants/emailProviders.ts and is initialized with at least one element (i.e., the Google provider). In this static context, the non-null assertion (!.icon) used on emailProviders[0] is safe.

That said, if there’s any future chance that emailProviders might become dynamic or could be empty, it would be prudent to add a fallback mechanism to prevent potential runtime errors.


21-178:

Details

❓ Verification inconclusive

Verify translation key structure for consistency

The translation keys follow a nested pattern like pages.settings.connections.disconnectSuccess. Ensure this pattern is consistently applied across the entire application and that all these keys are defined in your translation files.


🏁 Script executed:

#!/bin/bash
# Check translation file structure and look for the used keys
echo "Checking for translation file structure..."
fd -e json "pages" --full-path "apps/mail/translations" --exec cat {} \; | jq .

# Look for settings.connections keys in translation files
echo "\nLooking for settings.connections keys in translations..."
fd -e json -x grep -l "settings.connections" {} \; --full-path "apps/mail/translations"

Length of output: 501


🏁 Script executed:

#!/bin/bash
echo "Searching for translation keys in the repository..."
# Search for one of the translation keys used in the connections page, e.g. "pages.settings.connections.disconnectSuccess"
fd -e json -x grep -l "pages.settings.connections.disconnectSuccess" {}

Length of output: 186


Verify translation key definitions and consistency

The component uses nested translation keys (e.g., pages.settings.connections.disconnectSuccess) that should follow a consistent pattern across the application. However, our search for these keys via automated scripts didn’t return any results. Please manually verify that:

  • All keys used in this component (such as pages.settings.connections.disconnectSuccess, pages.settings.connections.disconnectError, pages.settings.connections.title, etc.) are defined in your translation files.
  • The translation files (whether in a locales directory, via a Next.js configuration, or another folder) follow the expected nested structure consistently.
  • There isn’t a misconfiguration in the file paths or directory names that might prevent the translation keys from being detected.

Once confirmed, update the translations if necessary to ensure consistency across the application.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/mail/app/(routes)/settings/general/page.tsx (3)

47-47: Hardcoded default language value

The form's default language is hardcoded as "en" rather than using the configured defaultLocale.

- language: "en",
+ language: defaultLocale,

This would require importing defaultLocale from "@/i18n/config".


78-80: Missing internationalization for toast messages

The toast messages for signing out are not internationalized.

- loading: "Signing out...",
- success: () => "Signed out successfully!",
- error: "Error signing out",
+ loading: t("common.actions.signingOut"),
+ success: () => t("common.actions.signedOutSuccess"),
+ error: t("common.actions.signOutError"),

146-154: Non-internationalized timezone names

The timezone display names are hardcoded in English rather than being internationalized.

Consider creating translation keys for timezone names or storing both code and translatable name in a configuration similar to availableLocales.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da6b9c0 and 3f34e80.

📒 Files selected for processing (4)
  • apps/mail/app/(routes)/settings/general/page.tsx (8 hunks)
  • apps/mail/i18n/config.ts (1 hunks)
  • apps/mail/i18n/utils.ts (1 hunks)
  • apps/mail/messages/es-ES.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/mail/messages/es-ES.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/mail/i18n/utils.ts
🔇 Additional comments (8)
apps/mail/i18n/config.ts (3)

1-1: Clear type definition for locale support

The Locale type ensures type safety when working with supported languages throughout the application.


3-20: Well-structured locale configuration

The availableLocales array clearly defines supported languages with both their code and human-readable name, making it easy to use in UI components like dropdown selectors.

Note that Spanish uses the region-specific code "es-ES" while other languages use only the language code. This inconsistency might be intentional but could cause confusion.

Are all language codes following a consistent standard? Consider using either all base language codes (e.g., "es" instead of "es-ES") or region-specific codes for all languages if regional differences matter.


22-23: Efficient locale utilities

The locales array is derived from availableLocales, avoiding duplication. The defaultLocale is properly typed with the Locale type to ensure it's always a valid option.

apps/mail/app/(routes)/settings/general/page.tsx (5)

21-27: Good integration of i18n dependencies

The imports properly set up the component with the necessary internationalization utilities.


59-60: Language change implementation

The locale change functionality is correctly hooked up to the form submission.


85-86: Translation hook initialization

The useTranslations hook is properly initialized for use throughout the component.


90-91: Heading internationalization

Card title and description successfully converted to use translation keys.


121-125: Dynamic language options from configuration

Good implementation of a dynamic language selector based on the centralized locale configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/mail/locales/es-ES.json (1)

1-222: Review of Spanish Localization File

The overall JSON structure is clear and well-organized with the UI elements grouped under common, navigation, and pages, which promotes consistency across the application. Most translations are properly rendered in Spanish.

One minor suggestion is to revisit the pluralization strings in the replyCompose section (lines 86–87). Currently, the keys use English words:

- "attachmentCount": "{count, plural, =0 {attachments} one {attachment} other {attachments}}",
- "fileCount": "{count, plural, =0 {files} one {file} other {files}}",
+ "attachmentCount": "{count, plural, =0 {adjuntos} one {adjunto} other {adjuntos}}",
+ "fileCount": "{count, plural, =0 {archivos} one {archivo} other {archivos}}",

Using Spanish terms (e.g., "adjunto/adjuntos" and "archivo/archivos") would better align with the language of this file.

apps/mail/locales/en.json (1)

1-223: English Localization File – Comprehensive and Consistent

The English localization file covers all necessary UI elements and maintains consistency with the other language files. One point to note is the possible stray line indicator ("223") at the end of the file; please ensure that only valid JSON content is part of the final file.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f34e80 and b3fe194.

📒 Files selected for processing (6)
  • apps/mail/global.d.ts (1 hunks)
  • apps/mail/i18n/request.ts (1 hunks)
  • apps/mail/locales/en.json (1 hunks)
  • apps/mail/locales/es-ES.json (1 hunks)
  • apps/mail/locales/fr.json (1 hunks)
  • apps/mail/locales/tr.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/mail/global.d.ts
  • apps/mail/i18n/request.ts
🔇 Additional comments (2)
apps/mail/locales/fr.json (1)

1-220: French Localization File – Looks Great

The French translations are accurately provided, and the structure mirrors that of the other localization files. All pluralization rules (for example, in replyCompose) are correctly localized into French with appropriate singular and plural forms.

apps/mail/locales/tr.json (1)

1-220: Turkish Localization File – Solid Implementation

The Turkish translation file is complete and well-structured, with translations for all UI elements. The pluralization (e.g., in replyCompose) is handled appropriately, and the language usage is culturally fitting.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
apps/mail/locales/tr.json (1)

105-118: Missing "close" key in threadDisplay section.

The English file has several entries in the threadDisplay section that align with the Turkish file, but in the Turkish file there's an extra "close" key that doesn't exist in the English version. This inconsistency could cause issues.

Either remove the "close" key from the Turkish file or add it to the English file to maintain consistency:

# For the English file (en.json):
"threadDisplay": {
+ "close": "Close",
  "exitFullscreen": "Exit fullscreen",
  "enterFullscreen": "Enter fullscreen",
  ...
apps/mail/app/(routes)/settings/general/page.tsx (3)

59-60: Consider adding feedback for locale change.

When the user changes the language, there's no feedback to indicate the change was successful. Adding a toast notification would improve the user experience.

changeLocale(values.language);
+ toast.success(t("pages.settings.general.languageChangedSuccess"));

You'll need to add the corresponding translation key to your locale files.


117-117: Missing translation for dropdown placeholder.

The SelectValue placeholder is hardcoded as "Select a language" instead of using a translation key.

- <SelectValue placeholder="Select a language" />
+ <SelectValue placeholder={t("pages.settings.general.selectLanguage")} />

Remember to add the "selectLanguage" key to your translation files.


142-142: Missing translation for timezone placeholder.

The SelectValue placeholder for timezone is hardcoded as "Select a timezone" instead of using a translation key.

- <SelectValue placeholder="Select a timezone" />
+ <SelectValue placeholder={t("pages.settings.general.selectTimezone")} />

Remember to add the "selectTimezone" key to your translation files.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3fe194 and a24aab5.

📒 Files selected for processing (5)
  • apps/mail/app/(routes)/settings/general/page.tsx (9 hunks)
  • apps/mail/locales/en.json (1 hunks)
  • apps/mail/locales/es-ES.json (1 hunks)
  • apps/mail/locales/fr.json (1 hunks)
  • apps/mail/locales/tr.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/mail/locales/es-ES.json
  • apps/mail/locales/fr.json
🔇 Additional comments (3)
apps/mail/app/(routes)/settings/general/page.tsx (1)

121-125: Great implementation of dynamic locale selection.

The dynamic population of language options from availableLocales is a good approach. It allows for easy addition of new languages without modifying this component.

apps/mail/locales/en.json (2)

89-91: Well-implemented pluralization in translation strings.

The use of ICU Message format for pluralization is excellent practice. This ensures grammatically correct translations across different languages.

"attachmentCount": "{count, plural, =0 {attachments} one {attachment} other {attachments}}",
"fileCount": "{count, plural, =0 {files} one {file} other {files}}",

1-225:

Details

❓ Verification inconclusive

Verify all translation files have the same structure.

Based on the comparison between English and Turkish files, there are some inconsistencies in the structure and available keys. This could lead to runtime errors or UI inconsistencies when switching languages.

Run the following script to verify all translation files have the same structure:


🏁 Script executed:

#!/bin/bash
# Find all locale files
locale_files=$(find apps/mail/locales -name "*.json")

# Extract all keys from the English file as reference
reference_keys=$(cat apps/mail/locales/en.json | jq -r 'paths | join(".")' | sort)

# Check each locale file against the reference
for file in $locale_files; do
  if [[ "$file" != *"en.json" ]]; then
    echo "Checking $file for missing keys..."
    file_keys=$(cat $file | jq -r 'paths | join(".")' | sort)
    
    # Find missing keys
    missing_keys=$(comm -23 <(echo "$reference_keys") <(echo "$file_keys"))
    
    if [[ -n "$missing_keys" ]]; then
      echo "Missing keys in $file:"
      echo "$missing_keys"
      echo ""
    else
      echo "No missing keys in $file"
      echo ""
    fi
    
    # Find extra keys
    extra_keys=$(comm -13 <(echo "$reference_keys") <(echo "$file_keys"))
    
    if [[ -n "$extra_keys" ]]; then
      echo "Extra keys in $file (not in reference):"
      echo "$extra_keys"
      echo ""
    fi
  fi
done

Length of output: 69178


Translation Files Structure Verification – Manual Review Needed

The automated script shows that the Spanish and French locale files match the English structure with no missing keys. However, errors (e.g., “comm: command not found”) prevented a reliable comparison for the Turkish locale file. Please manually verify that the Turkish translation (and any other locales) share the same structure as the English file to avoid potential runtime or UI issues when switching languages.

Comment on lines +76 to +82
"mailCategories": {
"primary": "Birincil",
"important": "Önemli",
"personal": "Kişisel",
"updates": "Güncellemeler",
"promotions": "Tanıtımlar"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Missing "social" category in Turkish translations.

The English translation file includes a "social" category under mailCategories, but this is missing in the Turkish translations. This inconsistency could cause UI issues when users switch languages.

Add the missing translation:

  "mailCategories": {
    "primary": "Birincil",
    "important": "Önemli",
    "personal": "Kişisel",
    "updates": "Güncellemeler",
    "promotions": "Tanıtımlar"
+   "social": "Sosyal"
  }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1 to +223
{
"common": {
"actions": {
"logout": "Çıkış Yap",
"back": "Geri",
"create": "Email Oluştur",
"saveChanges": "Değişiklikleri Kaydet",
"saving": "Kaydediliyor...",
"resetToDefaults": "Varsayılanlara Sıfırla",
"close": "Kapat",
"signingOut": "Çıkış yapılıyor...",
"signedOutSuccess": "Çıkış yapıldı!",
"signOutError": "Çıkış yapılırken hata oluştu"
},
"themes": {
"dark": "Koyu",
"light": "Açık",
"system": "Sistem"
},
"commandPalette": {
"title": "Komut Paleti",
"description": "Mail-0 için hızlı gezinme ve işlemler",
"placeholder": "Bir komut yazın veya arayın...",
"noResults": "Sonuç bulunamadı",
"groups": {
"mail": "E-posta",
"settings": "Ayarlar",
"actions": "İşlemler",
"help": "Yardım",
"navigation": "Gezinme"
},
"commands": {
"goToInbox": "Gelen Kutusuna Git",
"goToDrafts": "Taslaklara Git",
"goToSent": "Gönderilenlere Git",
"goToSpam": "Spam'a Git",
"goToArchive": "Arşive Git",
"goToBin": "Çöp Kutusuna Git",
"goToSettings": "Ayarlara Git",
"newEmail": "Yeni E-posta",
"composeMessage": "Mesaj oluştur",
"searchEmails": "E-postaları Ara",
"toggleTheme": "Temayı Değiştir",
"backToMail": "E-postaya Geri Dön",
"goToDocs": "Dokümanlara Git",
"helpWithShortcuts": "Kısayollar Hakkında Yardım"
}
},
"searchBar": {
"pickDateRange": "Tarih seçin veya aralık seçin",
"search": "Ara",
"clearSearch": "Aramayı temizle",
"advancedSearch": "Gelişmiş arama",
"quickFilters": "Hızlı filtreler",
"searchIn": "Ara",
"recipient": "Alıcı",
"sender": "Gönderen",
"subject": "Konu",
"dateRange": "Tarih aralığı",
"category": "Kategori",
"folder": "Klasör",
"allMail": "Tüm E-postalar",
"unread": "Okunmamış",
"hasAttachment": "Ekli",
"starred": "Yıldızlı",
"applyFilters": "Filtreleri Uygula",
"reset": "Sıfırla"
},
"navUser": {
"customerSupport": "Müşteri Desteği",
"documentation": "Dokümantasyon",
"appTheme": "Uygulama Teması",
"accounts": "Hesaplar",
"signIn": "Giriş Yap"
},
"mailCategories": {
"primary": "Birincil",
"important": "Önemli",
"personal": "Kişisel",
"updates": "Güncellemeler",
"promotions": "Tanıtımlar"
},
"replyCompose": {
"replyTo": "Yanıtla",
"thisEmail": "bu e-postaya",
"dropFiles": "Eklemek için dosyaları bırakın",
"attachments": "Ekler",
"attachmentCount": "{count, plural, =0 {ekler} one {ek} other {ekler}}",
"fileCount": "{count, plural, =0 {dosyalar} one {dosya} other {dosyalar}}",
"saveDraft": "Taslağı kaydet",
"send": "Gönder"
},
"mailDisplay": {
"details": "Detaylar",
"from": "Kimden",
"to": "Kime",
"cc": "Cc",
"date": "Tarih",
"mailedBy": "Gönderen",
"signedBy": "İmzalayan",
"security": "Güvenlik",
"standardEncryption": "Standart şifreleme (TLS)",
"loadingMailContent": "E-posta içeriği yükleniyor..."
},
"threadDisplay": {
"close": "Kapat",
"exitFullscreen": "Tam ekrandan çık",
"enterFullscreen": "Tam ekrana geç",
"archive": "Arşivle",
"reply": "Yanıtla",
"moreOptions": "Daha fazla seçenek",
"moveToSpam": "Spam'a taşı",
"replyAll": "Tümünü yanıtla",
"forward": "İlet",
"markAsUnread": "Okunmamış olarak işaretle",
"addLabel": "Etiket ekle",
"muteThread": "Konuyu sessize al"
}
},
"navigation": {
"sidebar": {
"inbox": "Posta Kutusu",
"drafts": "Taslaklar",
"sent": "Gönderilenler",
"spam": "Spam",
"archive": "Arşiv",
"bin": "Çöp",
"settings": "Ayarlar"
},
"settings": {
"general": "Genel",
"connections": "Bağlantılar",
"security": "Güvenlik",
"appearance": "Görünüm",
"shortcuts": "Kısayollar"
}
},
"pages": {
"error": {
"notFound": {
"title": "Sayfa Bulunamadı",
"description": "Üzgünüz! Aradığınız sayfa mevcut değil veya taşınmış.",
"goBack": "Geri Dön"
},
"settingsNotFound": "404 - Ayarlar sayfası bulunamadı"
},
"settings": {
"general": {
"title": "Genel Ayarlar",
"description": "Dil ve email görünümü ayarlarını yönetin.",
"language": "Dil",
"timezone": "Saat Dilimi",
"dynamicContent": "Dinamik İçerik",
"dynamicContentDescription": "E-postaların dinamik içerik göstermesine izin verin.",
"externalImages": "Harici Görselleri Göster",
"externalImagesDescription": "E-postaların harici kaynaklardan görselleri göstermesine izin verin."
},
"connections": {
"title": "E-posta Bağlantıları",
"description": "E-posta hesaplarınızı Zero'ya bağlayın.",
"disconnectTitle": "E-posta Hesabını Bağlantıyı Kes",
"disconnectDescription": "Bu e-posta bağlantısını kesmek istediğinizden emin misiniz?",
"cancel": "İptal",
"remove": "Kaldır",
"disconnectSuccess": "Hesap bağlantısı başarıyla kesildi",
"disconnectError": "Hesap bağlantısı kesilirken hata oluştu",
"addEmail": "E-posta Bağlantısı Ekle",
"connectEmail": "E-posta Bağlantısı",
"connectEmailDescription": "E-posta sağlayıcısını seçin",
"moreComingSoon": "Yakında daha fazla geliyor"
},
"security": {
"title": "Güvenlik",
"description": "Güvenlik tercihlerinizi ve hesap korumanızı yönetin.",
"twoFactorAuth": "İki Faktörlü Kimlik Doğrulama",
"twoFactorAuthDescription": "Hesabınıza ekstra bir güvenlik katmanı ekleyin",
"loginNotifications": "Giriş Bildirimleri",
"loginNotificationsDescription": "Yeni giriş denemeleri hakkında bildirim alın",
"deleteAccount": "Hesabı Sil"
},
"appearance": {
"title": "Görünüm",
"description": "Renkleri, yazı tiplerini ve görünüm seçeneklerini özelleştirin.",
"theme": "Tema",
"inboxType": "Gelen Kutusu Tipi"
},
"shortcuts": {
"title": "Klavye Kısayolları",
"description": "Hızlı işlemler için klavye kısayollarını görüntüleyin ve özelleştirin.",
"actions": {
"newEmail": "Yeni E-posta",
"sendEmail": "E-posta Gönder",
"reply": "Yanıtla",
"replyAll": "Tümünü Yanıtla",
"forward": "İlet",
"drafts": "Taslaklar",
"inbox": "Gelen Kutusu",
"sentMail": "Gönderilen Postalar",
"delete": "Sil",
"search": "Ara",
"markAsUnread": "Okunmadı Olarak İşaretle",
"muteThread": "Konuyu Sessize Al",
"printEmail": "E-postayı Yazdır",
"archiveEmail": "E-postayı Arşivle",
"markAsSpam": "Spam Olarak İşaretle",
"moveToFolder": "Klasöre Taşı",
"undoLastAction": "Son İşlemi Geri Al",
"viewEmailDetails": "E-posta Detaylarını Görüntüle",
"goToDrafts": "Taslaklara Git",
"expandEmailView": "E-posta Görünümünü Genişlet",
"helpWithShortcuts": "Kısayollar Hakkında Yardım"
}
}
},
"createEmail": {
"body": "İçerik",
"example": "zero@0.email",
"attachments": "Ekler",
"dropFilesToAttach": "Eklemek için dosyaları bırakın",
"writeYourMessageHere": "Mesajınızı buraya yazın..."
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Missing email notification translations in Turkish file.

The English translation file includes keys for "emailSentSuccessfully" and "failedToSendEmail" in the createEmail section (lines 221-222), but these are missing in the Turkish translation file.

Add the missing translations:

  "createEmail": {
    "body": "İçerik",
    "example": "zero@0.email",
    "attachments": "Ekler",
    "dropFilesToAttach": "Eklemek için dosyaları bırakın",
    "writeYourMessageHere": "Mesajınızı buraya yazın..."
+   "emailSentSuccessfully": "E-posta başarıyla gönderildi",
+   "failedToSendEmail": "E-posta gönderilemedi. Lütfen tekrar deneyin."
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
"common": {
"actions": {
"logout": "Çıkış Yap",
"back": "Geri",
"create": "Email Oluştur",
"saveChanges": "Değişiklikleri Kaydet",
"saving": "Kaydediliyor...",
"resetToDefaults": "Varsayılanlara Sıfırla",
"close": "Kapat",
"signingOut": "Çıkış yapılıyor...",
"signedOutSuccess": "Çıkış yapıldı!",
"signOutError": "Çıkış yapılırken hata oluştu"
},
"themes": {
"dark": "Koyu",
"light": "Açık",
"system": "Sistem"
},
"commandPalette": {
"title": "Komut Paleti",
"description": "Mail-0 için hızlı gezinme ve işlemler",
"placeholder": "Bir komut yazın veya arayın...",
"noResults": "Sonuç bulunamadı",
"groups": {
"mail": "E-posta",
"settings": "Ayarlar",
"actions": "İşlemler",
"help": "Yardım",
"navigation": "Gezinme"
},
"commands": {
"goToInbox": "Gelen Kutusuna Git",
"goToDrafts": "Taslaklara Git",
"goToSent": "Gönderilenlere Git",
"goToSpam": "Spam'a Git",
"goToArchive": "Arşive Git",
"goToBin": "Çöp Kutusuna Git",
"goToSettings": "Ayarlara Git",
"newEmail": "Yeni E-posta",
"composeMessage": "Mesaj oluştur",
"searchEmails": "E-postaları Ara",
"toggleTheme": "Temayı Değiştir",
"backToMail": "E-postaya Geri Dön",
"goToDocs": "Dokümanlara Git",
"helpWithShortcuts": "Kısayollar Hakkında Yardım"
}
},
"searchBar": {
"pickDateRange": "Tarih seçin veya aralık seçin",
"search": "Ara",
"clearSearch": "Aramayı temizle",
"advancedSearch": "Gelişmiş arama",
"quickFilters": "Hızlı filtreler",
"searchIn": "Ara",
"recipient": "Alıcı",
"sender": "Gönderen",
"subject": "Konu",
"dateRange": "Tarih aralığı",
"category": "Kategori",
"folder": "Klasör",
"allMail": "Tüm E-postalar",
"unread": "Okunmamış",
"hasAttachment": "Ekli",
"starred": "Yıldızlı",
"applyFilters": "Filtreleri Uygula",
"reset": "Sıfırla"
},
"navUser": {
"customerSupport": "Müşteri Desteği",
"documentation": "Dokümantasyon",
"appTheme": "Uygulama Teması",
"accounts": "Hesaplar",
"signIn": "Giriş Yap"
},
"mailCategories": {
"primary": "Birincil",
"important": "Önemli",
"personal": "Kişisel",
"updates": "Güncellemeler",
"promotions": "Tanıtımlar"
},
"replyCompose": {
"replyTo": "Yanıtla",
"thisEmail": "bu e-postaya",
"dropFiles": "Eklemek için dosyaları bırakın",
"attachments": "Ekler",
"attachmentCount": "{count, plural, =0 {ekler} one {ek} other {ekler}}",
"fileCount": "{count, plural, =0 {dosyalar} one {dosya} other {dosyalar}}",
"saveDraft": "Taslağı kaydet",
"send": "Gönder"
},
"mailDisplay": {
"details": "Detaylar",
"from": "Kimden",
"to": "Kime",
"cc": "Cc",
"date": "Tarih",
"mailedBy": "Gönderen",
"signedBy": "İmzalayan",
"security": "Güvenlik",
"standardEncryption": "Standart şifreleme (TLS)",
"loadingMailContent": "E-posta içeriği yükleniyor..."
},
"threadDisplay": {
"close": "Kapat",
"exitFullscreen": "Tam ekrandan çık",
"enterFullscreen": "Tam ekrana geç",
"archive": "Arşivle",
"reply": "Yanıtla",
"moreOptions": "Daha fazla seçenek",
"moveToSpam": "Spam'a taşı",
"replyAll": "Tümünü yanıtla",
"forward": "İlet",
"markAsUnread": "Okunmamış olarak işaretle",
"addLabel": "Etiket ekle",
"muteThread": "Konuyu sessize al"
}
},
"navigation": {
"sidebar": {
"inbox": "Posta Kutusu",
"drafts": "Taslaklar",
"sent": "Gönderilenler",
"spam": "Spam",
"archive": "Arşiv",
"bin": "Çöp",
"settings": "Ayarlar"
},
"settings": {
"general": "Genel",
"connections": "Bağlantılar",
"security": "Güvenlik",
"appearance": "Görünüm",
"shortcuts": "Kısayollar"
}
},
"pages": {
"error": {
"notFound": {
"title": "Sayfa Bulunamadı",
"description": "Üzgünüz! Aradığınız sayfa mevcut değil veya taşınmış.",
"goBack": "Geri Dön"
},
"settingsNotFound": "404 - Ayarlar sayfası bulunamadı"
},
"settings": {
"general": {
"title": "Genel Ayarlar",
"description": "Dil ve email görünümü ayarlarını yönetin.",
"language": "Dil",
"timezone": "Saat Dilimi",
"dynamicContent": "Dinamik İçerik",
"dynamicContentDescription": "E-postaların dinamik içerik göstermesine izin verin.",
"externalImages": "Harici Görselleri Göster",
"externalImagesDescription": "E-postaların harici kaynaklardan görselleri göstermesine izin verin."
},
"connections": {
"title": "E-posta Bağlantıları",
"description": "E-posta hesaplarınızı Zero'ya bağlayın.",
"disconnectTitle": "E-posta Hesabını Bağlantıyı Kes",
"disconnectDescription": "Bu e-posta bağlantısını kesmek istediğinizden emin misiniz?",
"cancel": "İptal",
"remove": "Kaldır",
"disconnectSuccess": "Hesap bağlantısı başarıyla kesildi",
"disconnectError": "Hesap bağlantısı kesilirken hata oluştu",
"addEmail": "E-posta Bağlantısı Ekle",
"connectEmail": "E-posta Bağlantısı",
"connectEmailDescription": "E-posta sağlayıcısını seçin",
"moreComingSoon": "Yakında daha fazla geliyor"
},
"security": {
"title": "Güvenlik",
"description": "Güvenlik tercihlerinizi ve hesap korumanızı yönetin.",
"twoFactorAuth": "İki Faktörlü Kimlik Doğrulama",
"twoFactorAuthDescription": "Hesabınıza ekstra bir güvenlik katmanı ekleyin",
"loginNotifications": "Giriş Bildirimleri",
"loginNotificationsDescription": "Yeni giriş denemeleri hakkında bildirim alın",
"deleteAccount": "Hesabı Sil"
},
"appearance": {
"title": "Görünüm",
"description": "Renkleri, yazı tiplerini ve görünüm seçeneklerini özelleştirin.",
"theme": "Tema",
"inboxType": "Gelen Kutusu Tipi"
},
"shortcuts": {
"title": "Klavye Kısayolları",
"description": "Hızlı işlemler için klavye kısayollarını görüntüleyin ve özelleştirin.",
"actions": {
"newEmail": "Yeni E-posta",
"sendEmail": "E-posta Gönder",
"reply": "Yanıtla",
"replyAll": "Tümünü Yanıtla",
"forward": "İlet",
"drafts": "Taslaklar",
"inbox": "Gelen Kutusu",
"sentMail": "Gönderilen Postalar",
"delete": "Sil",
"search": "Ara",
"markAsUnread": "Okunmadı Olarak İşaretle",
"muteThread": "Konuyu Sessize Al",
"printEmail": "E-postayı Yazdır",
"archiveEmail": "E-postayı Arşivle",
"markAsSpam": "Spam Olarak İşaretle",
"moveToFolder": "Klasöre Taşı",
"undoLastAction": "Son İşlemi Geri Al",
"viewEmailDetails": "E-posta Detaylarını Görüntüle",
"goToDrafts": "Taslaklara Git",
"expandEmailView": "E-posta Görünümünü Genişlet",
"helpWithShortcuts": "Kısayollar Hakkında Yardım"
}
}
},
"createEmail": {
"body": "İçerik",
"example": "zero@0.email",
"attachments": "Ekler",
"dropFilesToAttach": "Eklemek için dosyaları bırakın",
"writeYourMessageHere": "Mesajınızı buraya yazın..."
}
}
}
{
"common": {
"actions": {
"logout": "Çıkış Yap",
"back": "Geri",
"create": "Email Oluştur",
"saveChanges": "Değişiklikleri Kaydet",
"saving": "Kaydediliyor...",
"resetToDefaults": "Varsayılanlara Sıfırla",
"close": "Kapat",
"signingOut": "Çıkış yapılıyor...",
"signedOutSuccess": "Çıkış yapıldı!",
"signOutError": "Çıkış yapılırken hata oluştu"
},
"themes": {
"dark": "Koyu",
"light": "Açık",
"system": "Sistem"
},
"commandPalette": {
"title": "Komut Paleti",
"description": "Mail-0 için hızlı gezinme ve işlemler",
"placeholder": "Bir komut yazın veya arayın...",
"noResults": "Sonuç bulunamadı",
"groups": {
"mail": "E-posta",
"settings": "Ayarlar",
"actions": "İşlemler",
"help": "Yardım",
"navigation": "Gezinme"
},
"commands": {
"goToInbox": "Gelen Kutusuna Git",
"goToDrafts": "Taslaklara Git",
"goToSent": "Gönderilenlere Git",
"goToSpam": "Spam'a Git",
"goToArchive": "Arşive Git",
"goToBin": "Çöp Kutusuna Git",
"goToSettings": "Ayarlara Git",
"newEmail": "Yeni E-posta",
"composeMessage": "Mesaj oluştur",
"searchEmails": "E-postaları Ara",
"toggleTheme": "Temayı Değiştir",
"backToMail": "E-postaya Geri Dön",
"goToDocs": "Dokümanlara Git",
"helpWithShortcuts": "Kısayollar Hakkında Yardım"
}
},
"searchBar": {
"pickDateRange": "Tarih seçin veya aralık seçin",
"search": "Ara",
"clearSearch": "Aramayı temizle",
"advancedSearch": "Gelişmiş arama",
"quickFilters": "Hızlı filtreler",
"searchIn": "Ara",
"recipient": "Alıcı",
"sender": "Gönderen",
"subject": "Konu",
"dateRange": "Tarih aralığı",
"category": "Kategori",
"folder": "Klasör",
"allMail": "Tüm E-postalar",
"unread": "Okunmamış",
"hasAttachment": "Ekli",
"starred": "Yıldızlı",
"applyFilters": "Filtreleri Uygula",
"reset": "Sıfırla"
},
"navUser": {
"customerSupport": "Müşteri Desteği",
"documentation": "Dokümantasyon",
"appTheme": "Uygulama Teması",
"accounts": "Hesaplar",
"signIn": "Giriş Yap"
},
"mailCategories": {
"primary": "Birincil",
"important": "Önemli",
"personal": "Kişisel",
"updates": "Güncellemeler",
"promotions": "Tanıtımlar"
},
"replyCompose": {
"replyTo": "Yanıtla",
"thisEmail": "bu e-postaya",
"dropFiles": "Eklemek için dosyaları bırakın",
"attachments": "Ekler",
"attachmentCount": "{count, plural, =0 {ekler} one {ek} other {ekler}}",
"fileCount": "{count, plural, =0 {dosyalar} one {dosya} other {dosyalar}}",
"saveDraft": "Taslağı kaydet",
"send": "Gönder"
},
"mailDisplay": {
"details": "Detaylar",
"from": "Kimden",
"to": "Kime",
"cc": "Cc",
"date": "Tarih",
"mailedBy": "Gönderen",
"signedBy": "İmzalayan",
"security": "Güvenlik",
"standardEncryption": "Standart şifreleme (TLS)",
"loadingMailContent": "E-posta içeriği yükleniyor..."
},
"threadDisplay": {
"close": "Kapat",
"exitFullscreen": "Tam ekrandan çık",
"enterFullscreen": "Tam ekrana geç",
"archive": "Arşivle",
"reply": "Yanıtla",
"moreOptions": "Daha fazla seçenek",
"moveToSpam": "Spam'a taşı",
"replyAll": "Tümünü yanıtla",
"forward": "İlet",
"markAsUnread": "Okunmamış olarak işaretle",
"addLabel": "Etiket ekle",
"muteThread": "Konuyu sessize al"
}
},
"navigation": {
"sidebar": {
"inbox": "Posta Kutusu",
"drafts": "Taslaklar",
"sent": "Gönderilenler",
"spam": "Spam",
"archive": "Arşiv",
"bin": "Çöp",
"settings": "Ayarlar"
},
"settings": {
"general": "Genel",
"connections": "Bağlantılar",
"security": "Güvenlik",
"appearance": "Görünüm",
"shortcuts": "Kısayollar"
}
},
"pages": {
"error": {
"notFound": {
"title": "Sayfa Bulunamadı",
"description": "Üzgünüz! Aradığınız sayfa mevcut değil veya taşınmış.",
"goBack": "Geri Dön"
},
"settingsNotFound": "404 - Ayarlar sayfası bulunamadı"
},
"settings": {
"general": {
"title": "Genel Ayarlar",
"description": "Dil ve email görünümü ayarlarını yönetin.",
"language": "Dil",
"timezone": "Saat Dilimi",
"dynamicContent": "Dinamik İçerik",
"dynamicContentDescription": "E-postaların dinamik içerik göstermesine izin verin.",
"externalImages": "Harici Görselleri Göster",
"externalImagesDescription": "E-postaların harici kaynaklardan görselleri göstermesine izin verin."
},
"connections": {
"title": "E-posta Bağlantıları",
"description": "E-posta hesaplarınızı Zero'ya bağlayın.",
"disconnectTitle": "E-posta Hesabını Bağlantıyı Kes",
"disconnectDescription": "Bu e-posta bağlantısını kesmek istediğinizden emin misiniz?",
"cancel": "İptal",
"remove": "Kaldır",
"disconnectSuccess": "Hesap bağlantısı başarıyla kesildi",
"disconnectError": "Hesap bağlantısı kesilirken hata oluştu",
"addEmail": "E-posta Bağlantısı Ekle",
"connectEmail": "E-posta Bağlantısı",
"connectEmailDescription": "E-posta sağlayıcısını seçin",
"moreComingSoon": "Yakında daha fazla geliyor"
},
"security": {
"title": "Güvenlik",
"description": "Güvenlik tercihlerinizi ve hesap korumanızı yönetin.",
"twoFactorAuth": "İki Faktörlü Kimlik Doğrulama",
"twoFactorAuthDescription": "Hesabınıza ekstra bir güvenlik katmanı ekleyin",
"loginNotifications": "Giriş Bildirimleri",
"loginNotificationsDescription": "Yeni giriş denemeleri hakkında bildirim alın",
"deleteAccount": "Hesabı Sil"
},
"appearance": {
"title": "Görünüm",
"description": "Renkleri, yazı tiplerini ve görünüm seçeneklerini özelleştirin.",
"theme": "Tema",
"inboxType": "Gelen Kutusu Tipi"
},
"shortcuts": {
"title": "Klavye Kısayolları",
"description": "Hızlı işlemler için klavye kısayollarını görüntüleyin ve özelleştirin.",
"actions": {
"newEmail": "Yeni E-posta",
"sendEmail": "E-posta Gönder",
"reply": "Yanıtla",
"replyAll": "Tümünü Yanıtla",
"forward": "İlet",
"drafts": "Taslaklar",
"inbox": "Gelen Kutusu",
"sentMail": "Gönderilen Postalar",
"delete": "Sil",
"search": "Ara",
"markAsUnread": "Okunmadı Olarak İşaretle",
"muteThread": "Konuyu Sessize Al",
"printEmail": "E-postayı Yazdır",
"archiveEmail": "E-postayı Arşivle",
"markAsSpam": "Spam Olarak İşaretle",
"moveToFolder": "Klasöre Taşı",
"undoLastAction": "Son İşlemi Geri Al",
"viewEmailDetails": "E-posta Detaylarını Görüntüle",
"goToDrafts": "Taslaklara Git",
"expandEmailView": "E-posta Görünümünü Genişlet",
"helpWithShortcuts": "Kısayollar Hakkında Yardım"
}
}
},
"createEmail": {
"body": "İçerik",
"example": "zero@0.email",
"attachments": "Ekler",
"dropFilesToAttach": "Eklemek için dosyaları bırakın",
"writeYourMessageHere": "Mesajınızı buraya yazın...",
"emailSentSuccessfully": "E-posta başarıyla gönderildi",
"failedToSendEmail": "E-posta gönderilemedi. Lütfen tekrar deneyin."
}
}
}

Comment on lines +78 to +80
loading: t("common.actions.signingOut"),
success: () => t("common.actions.signedOutSuccess"),
error: t("common.actions.signOutError"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use translation function before defining it.

The translation function t is used in these toast message lines before it's actually defined on line 85. This could potentially cause errors.

Move the translation hook declaration above its first usage:

- toast.promise(
-   signOut({
-     fetchOptions: {
-       onSuccess: () => {
-         router.push("/");
-       },
-     },
-   }),
-   {
-     loading: t("common.actions.signingOut"),
-     success: () => t("common.actions.signedOutSuccess"),
-     error: t("common.actions.signOutError"),
-   },
- );
-};
-
-const t = useTranslations();

+ const t = useTranslations();
+
+ toast.promise(
+   signOut({
+     fetchOptions: {
+       onSuccess: () => {
+         router.push("/");
+       },
+     },
+   }),
+   {
+     loading: t("common.actions.signingOut"),
+     success: () => t("common.actions.signedOutSuccess"),
+     error: t("common.actions.signOutError"),
+   },
+ );
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
loading: t("common.actions.signingOut"),
success: () => t("common.actions.signedOutSuccess"),
error: t("common.actions.signOutError"),
const t = useTranslations();
toast.promise(
signOut({
fetchOptions: {
onSuccess: () => {
router.push("/");
},
},
}),
{
loading: t("common.actions.signingOut"),
success: () => t("common.actions.signedOutSuccess"),
error: t("common.actions.signOutError"),
},
);
};

"personal": "Personal",
"updates": "Updates",
"promotions": "Promotions",
"social": "Social"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Inconsistency between language files - "social" category present in English but missing in Turkish.

This key exists in the English file but not in the Turkish one. Ensure all translation files have the same structure to avoid potential runtime errors.

Add this key to the Turkish translation file to maintain consistency.

Comment on lines +221 to +222
"emailSentSuccessfully": "Email sent successfully",
"failedToSendEmail": "Failed to send email. Please try again."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Additional keys missing in Turkish translation.

These email notification strings exist in the English file but not in the Turkish one. Ensure these are added to maintain consistency.

Add these keys to the Turkish translation file.

Copy link
Contributor

@ahmetskilinc ahmetskilinc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all looks good.

@hiheyhello123 hiheyhello123 merged commit 55bb5e7 into Mail-0:staging Mar 13, 2025
2 of 3 checks passed
@hiheyhello123 hiheyhello123 mentioned this pull request Mar 18, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jul 1, 2025
32 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request High Priority High Priority Work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants