From a5fa876aa307d8b1a552de9900ba42be2cf57f46 Mon Sep 17 00:00:00 2001 From: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com> Date: Thu, 23 Oct 2025 20:23:11 +0530 Subject: [PATCH] feat: refactor translation key management (#6717) Co-authored-by: Piyush Gupta Co-authored-by: Piyush Gupta <56182734+gupta-piyush19@users.noreply.github.com> Co-authored-by: Victor Hugo dos Santos <115753265+victorvhs017@users.noreply.github.com> Co-authored-by: pandeymangg Co-authored-by: Matti Nannt Co-authored-by: Matti Nannt Co-authored-by: Johannes Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com> --- .cursor/rules/i18n-management.mdc | 457 +++ .env.example | 4 + .../workflows/tolgee-missing-key-check.yml | 51 - .github/workflows/tolgee.yml | 95 - .github/workflows/translation-check.yml | 63 + .husky/pre-commit | 34 +- .tolgeerc.json | 51 - .vscode/settings.json | 6 +- apps/storybook/.storybook/preview.ts | 29 +- .../components/ConnectWithFormbricks.tsx | 4 +- .../OnboardingSetupInstructions.tsx | 4 +- .../[environmentId]/connect/page.tsx | 2 +- .../components/XMTemplateList.tsx | 4 +- .../xm-templates/lib/xm-templates.test.ts | 8 +- .../xm-templates/lib/xm-templates.ts | 18 +- .../[environmentId]/xm-templates/page.tsx | 2 +- .../landing/components/landing-sidebar.tsx | 4 +- .../[organizationId]/landing/page.tsx | 2 +- .../organizations/[organizationId]/layout.tsx | 2 +- .../projects/new/channel/page.tsx | 2 +- .../[organizationId]/projects/new/layout.tsx | 2 +- .../projects/new/mode/page.tsx | 2 +- .../settings/components/ProjectSettings.tsx | 4 +- .../projects/new/settings/page.tsx | 2 +- .../components/ConfirmationPage.tsx | 4 +- .../components/EnvironmentLayout.tsx | 2 +- .../components/EnvironmentSwitch.tsx | 4 +- .../components/MainNavigation.tsx | 4 +- .../components/WidgetStatusIndicator.tsx | 4 +- .../components/environment-breadcrumb.tsx | 4 +- .../components/organization-breadcrumb.tsx | 4 +- .../components/project-breadcrumb.tsx | 4 +- .../components/AddIntegrationModal.tsx | 9 +- .../components/BaseSelectDropdown.tsx | 4 +- .../airtable/components/ManageIntegration.tsx | 4 +- .../project/integrations/airtable/page.tsx | 2 +- .../components/AddIntegrationModal.tsx | 4 +- .../components/ManageIntegration.tsx | 4 +- .../integrations/google-sheets/loading.tsx | 4 +- .../integrations/google-sheets/page.tsx | 2 +- .../notion/components/AddIntegrationModal.tsx | 4 +- .../notion/components/ManageIntegration.tsx | 4 +- .../project/integrations/notion/loading.tsx | 4 +- .../project/integrations/notion/page.tsx | 2 +- .../project/integrations/page.tsx | 6 +- .../components/AddChannelMappingModal.tsx | 4 +- .../slack/components/ManageIntegration.tsx | 10 +- .../project/integrations/slack/page.tsx | 2 +- .../components/AccountSettingsNavbar.tsx | 4 +- .../settings/(account)/layout.tsx | 2 +- .../notifications/components/EditAlerts.tsx | 4 +- .../components/IntegrationsTip.tsx | 4 +- .../components/NotificationSwitch.tsx | 4 +- .../(account)/notifications/loading.tsx | 4 +- .../settings/(account)/notifications/page.tsx | 2 +- .../profile/components/AccountSecurity.tsx | 4 +- .../profile/components/DeleteAccount.tsx | 4 +- .../components/EditProfileDetailsForm.tsx | 4 +- .../password-confirmation-modal.tsx | 4 +- .../settings/(account)/profile/loading.tsx | 4 +- .../settings/(account)/profile/page.tsx | 2 +- .../(organization)/billing/loading.tsx | 2 +- .../components/OrganizationSettingsNavbar.tsx | 4 +- .../(organization)/enterprise/loading.tsx | 2 +- .../(organization)/enterprise/page.tsx | 2 +- .../general/components/DeleteOrganization.tsx | 6 +- .../components/EditOrganizationNameForm.tsx | 4 +- .../(organization)/general/loading.tsx | 2 +- .../settings/(organization)/general/page.tsx | 2 +- .../settings/(organization)/layout.tsx | 2 +- .../settings/components/SettingsCard.tsx | 4 +- .../components/EmptyInAppSurveys.tsx | 4 +- .../components/SurveyAnalysisNavigation.tsx | 4 +- .../responses/components/ResponseDataView.tsx | 7 +- .../responses/components/ResponseTable.tsx | 4 +- .../components/ResponseTableColumns.tsx | 8 +- .../(analysis)/responses/lib/utils.ts | 8 +- .../[surveyId]/(analysis)/responses/page.tsx | 2 +- .../summary/components/AddressSummary.tsx | 4 +- .../summary/components/CTASummary.tsx | 4 +- .../summary/components/CalSummary.tsx | 4 +- .../summary/components/ConsentSummary.tsx | 4 +- .../summary/components/ContactInfoSummary.tsx | 4 +- .../components/DateQuestionSummary.tsx | 4 +- .../summary/components/FileUploadSummary.tsx | 4 +- .../components/HiddenFieldsSummary.tsx | 4 +- .../components/MatrixQuestionSummary.tsx | 4 +- .../components/MultipleChoiceSummary.tsx | 4 +- .../summary/components/NPSSummary.tsx | 4 +- .../summary/components/OpenTextSummary.tsx | 4 +- .../components/PictureChoiceSummary.tsx | 4 +- .../components/QuestionSummaryHeader.tsx | 4 +- .../summary/components/RankingSummary.tsx | 4 +- .../summary/components/RatingSummary.tsx | 4 +- .../summary/components/SuccessMessage.tsx | 4 +- .../summary/components/SummaryDropOffs.tsx | 4 +- .../summary/components/SummaryList.tsx | 4 +- .../summary/components/SummaryMetadata.tsx | 4 +- .../summary/components/SurveyAnalysisCTA.tsx | 4 +- .../summary/components/share-survey-modal.tsx | 4 +- .../shareEmbedModal/anonymous-links-tab.tsx | 4 +- .../components/shareEmbedModal/app-tab.tsx | 10 +- .../shareEmbedModal/disable-link-modal.tsx | 4 +- .../documentation-links-section.tsx | 4 +- .../shareEmbedModal/documentation-links.tsx | 4 +- .../shareEmbedModal/dynamic-popup-tab.tsx | 4 +- .../components/shareEmbedModal/email-tab.tsx | 4 +- .../shareEmbedModal/link-settings-tab.tsx | 4 +- .../shareEmbedModal/personal-links-tab.tsx | 4 +- .../shareEmbedModal/qr-code-tab.tsx | 4 +- .../components/shareEmbedModal/share-view.tsx | 4 +- .../shareEmbedModal/social-media-tab.tsx | 4 +- .../shareEmbedModal/success-view.tsx | 4 +- .../shareEmbedModal/website-embed-tab.tsx | 4 +- .../(analysis)/summary/lib/emailTemplate.tsx | 2 +- .../(analysis)/summary/lib/utils.ts | 4 +- .../[surveyId]/(analysis)/summary/loading.tsx | 4 +- .../[surveyId]/(analysis)/summary/page.tsx | 2 +- .../[surveyId]/components/CustomFilter.tsx | 9 +- .../components/QuestionFilterComboBox.tsx | 4 +- .../components/QuestionsComboBox.tsx | 4 +- .../[surveyId]/components/ResponseFilter.tsx | 4 +- .../components/SurveyStatusDropdown.tsx | 4 +- apps/web/app/error.tsx | 8 +- apps/web/app/layout.tsx | 21 +- apps/web/app/lib/survey-builder.test.ts | 1 - apps/web/app/lib/survey-builder.ts | 26 +- apps/web/app/lib/templates.ts | 106 +- apps/web/i18n.json | 13 + apps/web/i18n.lock | 2761 +++++++++++++++++ apps/web/lib/i18n/constants.ts | 2 - apps/web/lib/i18n/utils.ts | 8 +- apps/web/lingodotdev/client.tsx | 66 + apps/web/lingodotdev/language.test.ts | 77 + apps/web/{tolgee => lingodotdev}/language.ts | 14 +- apps/web/lingodotdev/server.test.ts | 25 + apps/web/lingodotdev/server.ts | 29 + apps/web/locales/de-DE.json | 61 +- apps/web/locales/en-US.json | 13 +- apps/web/locales/fr-FR.json | 61 +- apps/web/locales/ja-JP.json | 61 +- apps/web/locales/pt-BR.json | 61 +- apps/web/locales/pt-PT.json | 61 +- apps/web/locales/ro-RO.json | 61 +- apps/web/locales/zh-Hans-CN.json | 61 +- apps/web/locales/zh-Hant-TW.json | 61 +- .../components/DeleteAccountModal/index.tsx | 9 +- .../components/ShareSurveyLink/index.tsx | 4 +- .../components/HiddenFields.tsx | 4 +- .../components/QuestionSkip.tsx | 4 +- .../components/ResponseTagsWrapper.tsx | 4 +- .../components/ResponseVariables.tsx | 4 +- .../components/SingleResponseCardBody.tsx | 4 +- .../components/SingleResponseCardHeader.tsx | 4 +- .../components/VerifiedEmail.tsx | 4 +- .../components/SingleResponseCard/index.tsx | 4 +- .../auth/components/back-to-login-button.tsx | 2 +- .../page.tsx | 2 +- .../components/forgot-password-form.tsx | 4 +- .../auth/forgot-password/email-sent/page.tsx | 2 +- .../reset/components/reset-password-form.tsx | 4 +- .../forgot-password/reset/success/page.tsx | 2 +- apps/web/modules/auth/invite/page.tsx | 2 +- .../auth/login/components/login-form.tsx | 4 +- .../page.tsx | 7 +- .../signup/components/password-checks.tsx | 4 +- .../auth/signup/components/signup-form.tsx | 4 +- .../signup/components/terms-privacy-links.tsx | 4 +- .../components/request-verification-email.tsx | 4 +- .../components/verification-message.tsx | 19 + .../auth/verification-requested/page.tsx | 10 +- .../components/email-change-sign-in.tsx | 4 +- apps/web/modules/auth/verify/page.tsx | 2 +- .../modules/ee/billing/api/lib/constants.ts | 4 +- .../ee/billing/components/billing-slider.tsx | 4 +- .../ee/billing/components/pricing-card.tsx | 4 +- .../ee/billing/components/pricing-table.tsx | 4 +- apps/web/modules/ee/billing/page.tsx | 2 +- .../components/attributes-section.tsx | 2 +- .../components/delete-contact-button.tsx | 4 +- .../components/response-section.tsx | 2 +- .../components/response-timeline.tsx | 4 +- .../modules/ee/contacts/[contactId]/page.tsx | 2 +- .../contacts-secondary-navigation.tsx | 2 +- .../ee/contacts/components/contacts-table.tsx | 4 +- .../upload-contacts-attribute-combobox.tsx | 4 +- .../components/upload-contacts-attribute.tsx | 4 +- .../components/upload-contacts-button.tsx | 4 +- apps/web/modules/ee/contacts/layout.tsx | 2 +- apps/web/modules/ee/contacts/page.tsx | 2 +- .../segments/components/add-filter-modal.tsx | 4 +- .../components/attribute-tab-content.tsx | 4 +- .../components/create-segment-modal.tsx | 4 +- .../components/edit-segment-modal.tsx | 4 +- .../components/segment-activity-tab.tsx | 4 +- .../segments/components/segment-editor.tsx | 4 +- .../segments/components/segment-filter.tsx | 14 +- .../segments/components/segment-settings.tsx | 4 +- .../segments/components/segment-table.tsx | 2 +- .../segments/components/targeting-card.tsx | 4 +- .../modules/ee/contacts/segments/loading.tsx | 2 +- .../web/modules/ee/contacts/segments/page.tsx | 2 +- apps/web/modules/ee/languages/loading.tsx | 4 +- apps/web/modules/ee/languages/page.tsx | 2 +- .../components/default-language-select.tsx | 4 +- .../components/edit-language.tsx | 9 +- .../components/language-labels.tsx | 7 +- .../components/language-row.tsx | 4 +- .../components/language-select.tsx | 4 +- .../components/language-toggle.tsx | 4 +- .../components/localized-editor.tsx | 4 +- .../components/multi-language-card.tsx | 4 +- .../components/secondary-language-select.tsx | 4 +- .../components/ending-card-selector.tsx | 4 +- .../components/quota-condition-builder.tsx | 4 +- .../ee/quotas/components/quota-list.tsx | 4 +- .../ee/quotas/components/quota-modal.tsx | 4 +- .../ee/quotas/components/quotas-card.tsx | 7 +- .../ee/quotas/components/quotas-summary.tsx | 4 +- .../single-response-card-quotas.tsx | 4 +- .../components/add-member-role.tsx | 4 +- .../components/edit-membership-role.tsx | 4 +- .../ee/sso/components/azure-button.tsx | 4 +- .../ee/sso/components/github-button.tsx | 4 +- .../ee/sso/components/google-button.tsx | 4 +- .../ee/sso/components/open-id-button.tsx | 4 +- .../modules/ee/sso/components/saml-button.tsx | 4 +- .../modules/ee/sso/components/sso-options.tsx | 4 +- .../project-teams/components/access-table.tsx | 4 +- .../project-teams/components/access-view.tsx | 4 +- .../project-teams/components/manage-team.tsx | 4 +- .../ee/teams/project-teams/loading.tsx | 4 +- .../modules/ee/teams/project-teams/page.tsx | 2 +- .../components/create-team-button.tsx | 4 +- .../components/create-team-modal.tsx | 4 +- .../components/manage-team-button.tsx | 4 +- .../components/team-settings/delete-team.tsx | 4 +- .../team-settings/team-settings-modal.tsx | 4 +- .../team-list/components/teams-table.tsx | 4 +- .../teams/team-list/components/teams-view.tsx | 2 +- .../components/confirm-password-form.tsx | 4 +- .../components/disable-two-factor-modal.tsx | 4 +- .../components/display-backup-codes.tsx | 4 +- .../components/enable-two-factor-modal.tsx | 4 +- .../two-factor-auth/components/enter-code.tsx | 4 +- .../components/scan-qr-code.tsx | 4 +- .../components/two-factor-backup.tsx | 4 +- .../two-factor-auth/components/two-factor.tsx | 4 +- .../email-customization-settings.tsx | 4 +- .../components/branding-settings-card.tsx | 2 +- .../components/edit-branding.tsx | 4 +- .../modules/email/components/email-footer.tsx | 4 +- .../email/components/email-template.tsx | 4 +- .../components/preview-email-template.tsx | 6 +- .../emails/auth/forgot-password-email.tsx | 2 +- .../emails/auth/new-email-verification.tsx | 2 +- .../auth/password-reset-notify-email.tsx | 2 +- .../email/emails/auth/verification-email.tsx | 2 +- .../email-customization-preview-email.tsx | 2 +- .../emails/invite/invite-accepted-email.tsx | 2 +- .../email/emails/invite/invite-email.tsx | 2 +- apps/web/modules/email/emails/lib/utils.tsx | 4 +- .../survey/embed-survey-preview-email.tsx | 2 +- .../email/emails/survey/link-survey-email.tsx | 2 +- .../emails/survey/response-finished-email.tsx | 2 +- apps/web/modules/email/index.tsx | 2 +- .../modules/environments/lib/utils.test.ts | 8 +- apps/web/modules/environments/lib/utils.ts | 2 +- .../components/add-webhook-button.tsx | 4 +- .../webhooks/components/add-webhook-modal.tsx | 4 +- .../components/survey-checkbox-group.tsx | 4 +- .../components/trigger-checkbox-group.tsx | 4 +- .../components/webhook-detail-modal.tsx | 4 +- .../components/webhook-overview-tab.tsx | 7 +- .../webhooks/components/webhook-row-data.tsx | 7 +- .../components/webhook-settings-tab.tsx | 4 +- .../components/webhook-table-heading.tsx | 2 +- .../webhooks/components/webhook-table.tsx | 4 +- .../modules/integrations/webhooks/page.tsx | 2 +- .../CreateOrganizationModal/index.tsx | 4 +- .../modules/organization/lib/utils.test.ts | 2 +- apps/web/modules/organization/lib/utils.ts | 2 +- .../api-keys/components/add-api-key-modal.tsx | 4 +- .../api-keys/components/edit-api-keys.tsx | 4 +- .../components/view-permission-modal.tsx | 4 +- .../settings/api-keys/loading.tsx | 6 +- .../organization/settings/api-keys/page.tsx | 2 +- .../edit-memberships/edit-memberships.tsx | 2 +- .../edit-memberships/member-actions.tsx | 4 +- .../edit-memberships/members-info.tsx | 4 +- .../edit-memberships/organization-actions.tsx | 4 +- .../invite-member/bulk-invite-tab.tsx | 4 +- .../invite-member/individual-invite-tab.tsx | 4 +- .../invite-member/invite-member-modal.tsx | 4 +- .../invite-member/share-invite-modal.tsx | 4 +- .../teams/components/members-view.tsx | 2 +- .../organization/settings/teams/page.tsx | 2 +- .../components/create-project-modal/index.tsx | 4 +- .../components/project-limit-modal/index.tsx | 4 +- .../(setup)/app-connection/loading.tsx | 4 +- .../settings/(setup)/app-connection/page.tsx | 2 +- .../(setup)/components/ActionActivityTab.tsx | 4 +- .../(setup)/components/ActionDetailModal.tsx | 4 +- .../(setup)/components/ActionSettingsTab.tsx | 4 +- .../(setup)/components/ActionTableHeading.tsx | 4 +- .../(setup)/components/AddActionModal.tsx | 4 +- .../components/action-settings-card.tsx | 4 +- .../components/project-config-navigation.tsx | 4 +- .../components/delete-project-render.tsx | 4 +- .../general/components/delete-project.tsx | 2 +- .../components/edit-project-name-form.tsx | 4 +- .../components/edit-waiting-time-form.tsx | 4 +- .../projects/settings/general/loading.tsx | 4 +- .../projects/settings/general/page.tsx | 2 +- .../settings/look/components/edit-logo.tsx | 4 +- .../look/components/edit-placement-form.tsx | 4 +- .../look/components/theme-styling.tsx | 4 +- .../projects/settings/look/loading.tsx | 4 +- .../modules/projects/settings/look/page.tsx | 2 +- .../tags/components/edit-tags-wrapper.tsx | 4 +- .../tags/components/merge-tags-combobox.tsx | 4 +- .../settings/tags/components/single-tag.tsx | 4 +- .../projects/settings/tags/loading.tsx | 4 +- .../modules/projects/settings/tags/page.tsx | 2 +- .../setup/(fresh-instance)/intro/page.tsx | 9 +- .../setup/(fresh-instance)/signup/page.tsx | 2 +- .../invite/components/invite-members.tsx | 4 +- .../[organizationId]/invite/page.tsx | 2 +- .../create/components/create-organization.tsx | 4 +- .../components/removed-from-organization.tsx | 4 +- .../setup/organization/create/page.tsx | 2 +- .../edit-public-survey-alert-dialog/index.tsx | 4 +- .../components/fallback-input.tsx | 4 +- .../components/multi-lang-wrapper.tsx | 4 +- .../components/recall-item-select.tsx | 4 +- .../components/recall-wrapper.tsx | 4 +- .../components/question-form-input/index.tsx | 4 +- .../question-form-input/utils.test.ts | 8 +- .../components/question-form-input/utils.ts | 4 +- .../start-from-scratch-template.tsx | 4 +- .../components/template-filters.tsx | 4 +- .../components/template-tags.tsx | 7 +- .../template-list/components/template.tsx | 4 +- .../survey/components/template-list/index.tsx | 4 +- .../components/template-list/lib/utils.ts | 8 +- .../editor/components/add-action-modal.tsx | 4 +- .../components/add-ending-card-button.tsx | 4 +- .../editor/components/add-question-button.tsx | 4 +- .../components/address-question-form.tsx | 4 +- .../editor/components/cal-question-form.tsx | 4 +- .../editor/components/conditional-logic.tsx | 4 +- .../components/consent-question-form.tsx | 4 +- .../components/contact-info-question-form.tsx | 4 +- .../components/create-new-action-tab.tsx | 4 +- .../editor/components/cta-question-form.tsx | 4 +- .../editor/components/date-question-form.tsx | 4 +- .../editor/components/edit-ending-card.tsx | 4 +- .../editor/components/edit-welcome-card.tsx | 5 +- .../editor/components/editor-card-menu.tsx | 4 +- .../editor/components/end-screen-form.tsx | 4 +- .../components/file-upload-question-form.tsx | 4 +- .../components/form-styling-settings.tsx | 4 +- .../editor/components/hidden-fields-card.tsx | 4 +- .../editor/components/how-to-send-card.tsx | 4 +- .../components/logic-editor-actions.tsx | 4 +- .../components/logic-editor-conditions.tsx | 4 +- .../survey/editor/components/logic-editor.tsx | 4 +- .../components/matrix-question-form.tsx | 4 +- .../components/matrix-sortable-item.tsx | 4 +- .../multiple-choice-question-form.tsx | 4 +- .../editor/components/nps-question-form.tsx | 4 +- .../editor/components/open-question-form.tsx | 4 +- .../survey/editor/components/option-ids.tsx | 4 +- .../components/picture-selection-form.tsx | 4 +- .../survey/editor/components/placement.tsx | 4 +- .../editor/components/question-card.tsx | 4 +- .../components/question-option-choice.tsx | 4 +- .../editor/components/questions-view.tsx | 4 +- .../components/ranking-question-form.tsx | 4 +- .../components/rating-question-form.tsx | 4 +- .../components/recontact-options-card.tsx | 4 +- .../editor/components/redirect-url-form.tsx | 4 +- .../components/response-options-card.tsx | 4 +- .../editor/components/saved-actions-tab.tsx | 4 +- .../survey/editor/components/styling-view.tsx | 4 +- .../editor/components/survey-editor-tabs.tsx | 4 +- .../editor/components/survey-menu-bar.tsx | 4 +- .../components/survey-placement-card.tsx | 4 +- .../components/survey-variables-card-item.tsx | 4 +- .../components/survey-variables-card.tsx | 4 +- .../components/targeting-locked-card.tsx | 4 +- .../editor/components/unsplash-images.tsx | 4 +- .../editor/components/update-question-id.tsx | 4 +- .../editor/components/when-to-send-card.tsx | 4 +- .../survey/editor/lib/action-builder.test.ts | 4 +- .../survey/editor/lib/action-builder.ts | 8 +- .../modules/survey/editor/lib/action-utils.ts | 14 +- .../editor/lib/logic-rule-engine.test.ts | 4 +- .../survey/editor/lib/logic-rule-engine.ts | 4 +- .../editor/lib/shared-conditions-factory.ts | 4 +- apps/web/modules/survey/editor/lib/utils.tsx | 24 +- .../survey/editor/lib/validation.test.ts | 4 +- .../modules/survey/editor/lib/validation.ts | 4 +- apps/web/modules/survey/editor/page.tsx | 2 +- .../follow-ups/components/follow-up-email.tsx | 2 +- .../follow-ups/components/follow-up-item.tsx | 4 +- .../follow-ups/components/follow-up-modal.tsx | 4 +- .../follow-ups/components/follow-ups-view.tsx | 4 +- .../survey/follow-ups/lib/follow-ups.test.ts | 6 + apps/web/modules/survey/lib/questions.tsx | 18 +- .../survey/link/components/legal-footer.tsx | 4 +- .../survey/link/components/pin-screen.tsx | 4 +- .../link/components/survey-inactive.tsx | 2 +- .../link/components/survey-link-used.tsx | 4 +- .../survey/link/components/verify-email.tsx | 4 +- .../survey/link/contact-survey/page.tsx | 2 +- .../list/components/copy-survey-form.tsx | 4 +- .../list/components/copy-survey-modal.tsx | 4 +- .../survey/list/components/survey-card.tsx | 4 +- .../list/components/survey-dropdown-menu.tsx | 4 +- .../survey/list/components/survey-filters.tsx | 11 +- .../survey/list/components/survey-list.tsx | 4 +- .../list/components/survey-type-indicator.tsx | 4 +- apps/web/modules/survey/list/loading.tsx | 4 +- apps/web/modules/survey/list/page.tsx | 2 +- .../templates/components/back-button.tsx | 4 +- .../components/template-container.tsx | 4 +- .../survey/templates/lib/minimal-survey.ts | 4 +- apps/web/modules/survey/templates/page.tsx | 2 +- .../ui/components/action-class-info/index.tsx | 4 +- .../action-name-description-fields/index.tsx | 4 +- .../additional-integration-settings/index.tsx | 4 +- .../background-styling-card/index.tsx | 4 +- .../survey-bg-selector-tab.tsx | 4 +- .../card-arrangement-tabs/index.tsx | 4 +- .../card-styling-settings/index.tsx | 4 +- .../ui/components/client-logo/index.tsx | 4 +- .../ui/components/code-action-form/index.tsx | 4 +- .../ui/components/code-block/index.tsx | 4 +- .../ui/components/conditions-editor/index.tsx | 4 +- .../confirm-delete-segment-modal/index.tsx | 4 +- .../components/confirmation-modal/index.tsx | 4 +- .../components/connect-integration/index.tsx | 4 +- .../connect-integration/lib/utils.ts | 4 +- .../components/column-settings-dropdown.tsx | 4 +- .../components/data-table-settings-modal.tsx | 4 +- .../components/data-table-toolbar.tsx | 4 +- .../components/selected-row-settings.tsx | 4 +- .../ui/components/date-picker/index.tsx | 4 +- .../decrement-quotas-checkbox/index.tsx | 4 +- .../ui/components/default-tag/index.tsx | 4 +- .../ui/components/delete-dialog/index.tsx | 4 +- .../editor/components/link-editor.tsx | 4 +- .../editor/components/toolbar-plugin.tsx | 4 +- .../components/empty-space-filler/index.tsx | 4 +- .../components/environment-notice/index.tsx | 2 +- .../ui/components/error-component/index.tsx | 4 +- .../file-input/components/video-settings.tsx | 4 +- .../ui/components/file-input/index.tsx | 4 +- .../components/file-upload-response/index.tsx | 4 +- .../ui/components/go-back-button/index.tsx | 4 +- .../id-badge/components/badge-content.tsx | 4 +- .../modules/ui/components/id-badge/index.tsx | 4 +- .../ui/components/input-combo-box/index.tsx | 4 +- .../limits-reached-banner/index.tsx | 4 +- .../components/load-segment-modal/index.tsx | 4 +- .../ui/components/media-background/index.tsx | 4 +- .../components/css-selector.tsx | 4 +- .../components/inner-html-selector.tsx | 4 +- .../components/page-url-selector.tsx | 9 +- .../components/no-code-action-form/index.tsx | 4 +- .../ui/components/no-mobile-overlay/index.tsx | 4 +- .../pending-downgrade-banner/index.tsx | 4 +- .../ui/components/preview-survey/index.tsx | 4 +- .../question-toggle-table/index.tsx | 4 +- .../reset-progress-button/index.tsx | 4 +- .../save-as-new-segment-modal/index.tsx | 4 +- .../ui/components/segment-title/index.tsx | 4 +- .../shuffle-option-select/index.tsx | 4 +- .../survey-status-indicator/index.tsx | 4 +- .../ui/components/tags-combobox/index.tsx | 4 +- .../components/targeting-indicator/index.tsx | 4 +- .../theme-styling-preview-survey/index.tsx | 4 +- apps/web/package.json | 9 +- apps/web/tolgee/client.tsx | 45 - apps/web/tolgee/server.tsx | 26 - apps/web/tolgee/shared.test.ts | 102 - apps/web/tolgee/shared.ts | 40 - apps/web/vite.config.mts | 2 +- apps/web/vitestSetup.ts | 71 +- package.json | 8 +- packages/i18n-utils/package.json | 13 +- .../i18n-utils/src/scan-translations.test.ts | 664 ++++ packages/i18n-utils/src/scan-translations.ts | 525 ++++ packages/i18n-utils/vite.config.ts | 10 +- packages/surveys/i18n.lock | 1 - packages/surveys/locales/ar.json | 1 - packages/surveys/locales/de.json | 1 - packages/surveys/locales/en.json | 1 - packages/surveys/locales/es.json | 1 - packages/surveys/locales/fr.json | 1 - packages/surveys/locales/hi.json | 1 - packages/surveys/locales/it.json | 1 - packages/surveys/locales/ja.json | 1 - packages/surveys/locales/pt.json | 1 - packages/surveys/locales/ro.json | 1 - packages/surveys/locales/ru.json | 1 - packages/surveys/locales/uz.json | 1 - packages/surveys/locales/zh-Hans.json | 1 - pnpm-lock.yaml | 193 +- 510 files changed, 6129 insertions(+), 1883 deletions(-) create mode 100644 .cursor/rules/i18n-management.mdc delete mode 100644 .github/workflows/tolgee-missing-key-check.yml delete mode 100644 .github/workflows/tolgee.yml create mode 100644 .github/workflows/translation-check.yml delete mode 100644 .tolgeerc.json create mode 100644 apps/web/i18n.json create mode 100644 apps/web/i18n.lock delete mode 100644 apps/web/lib/i18n/constants.ts create mode 100644 apps/web/lingodotdev/client.tsx create mode 100644 apps/web/lingodotdev/language.test.ts rename apps/web/{tolgee => lingodotdev}/language.ts (55%) create mode 100644 apps/web/lingodotdev/server.test.ts create mode 100644 apps/web/lingodotdev/server.ts create mode 100644 apps/web/modules/auth/verification-requested/components/verification-message.tsx delete mode 100644 apps/web/tolgee/client.tsx delete mode 100644 apps/web/tolgee/server.tsx delete mode 100644 apps/web/tolgee/shared.test.ts delete mode 100644 apps/web/tolgee/shared.ts create mode 100644 packages/i18n-utils/src/scan-translations.test.ts create mode 100644 packages/i18n-utils/src/scan-translations.ts diff --git a/.cursor/rules/i18n-management.mdc b/.cursor/rules/i18n-management.mdc new file mode 100644 index 0000000000..f36338d6c6 --- /dev/null +++ b/.cursor/rules/i18n-management.mdc @@ -0,0 +1,457 @@ +--- +title: i18n Management with Lingo.dev +description: Guidelines for managing internationalization (i18n) with Lingo.dev, including translation workflow, key validation, and best practices +--- + +# i18n Management with Lingo.dev + +This rule defines the workflow and best practices for managing internationalization (i18n) in the Formbricks project using Lingo.dev. + +## Overview + +Formbricks uses [Lingo.dev](https://lingo.dev) for managing translations across multiple languages. The translation workflow includes: + +1. **Translation Keys**: Defined in code using the `t()` function from `react-i18next` +2. **Translation Files**: JSON files stored in `apps/web/locales/` for each supported language +3. **Validation**: Automated scanning to detect missing and unused translation keys +4. **CI/CD**: Pre-commit hooks and GitHub Actions to enforce translation quality + +## Translation Workflow + +### 1. Using Translations in Code + +When adding translatable text in the web app, use the `t()` function or `` component: + +**Using the `t()` function:** +```tsx +import { useTranslate } from "@/lib/i18n/translate"; + +const MyComponent = () => { + const { t } = useTranslate(); + + return ( +
+

{t("common.welcome")}

+

{t("pages.dashboard.description")}

+
+ ); +}; +``` + +**Using the `` component (for text with HTML elements):** +```tsx +import { Trans } from "react-i18next"; + +const MyComponent = () => { + return ( +
+

+ , + b: + }} + /> +

+
+ ); +}; +``` + +**Key Naming Conventions:** +- Use dot notation for nested keys: `section.subsection.key` +- Use descriptive names: `auth.login.success_message` not `auth.msg1` +- Group related keys together: `auth.*`, `errors.*`, `common.*` +- Use lowercase with underscores: `user_profile_settings` not `UserProfileSettings` + +### 2. Translation File Structure + +Translation files are located in `apps/web/locales/` and use the following naming convention: +- `en-US.json` (English - United States, default) +- `de-DE.json` (German) +- `fr-FR.json` (French) +- `pt-BR.json` (Portuguese - Brazil) +- etc. + +**File Structure:** +```json +{ + "common": { + "welcome": "Welcome", + "save": "Save", + "cancel": "Cancel" + }, + "auth": { + "login": { + "title": "Login", + "email_placeholder": "Enter your email", + "password_placeholder": "Enter your password" + } + } +} +``` + +### 3. Adding New Translation Keys + +When adding new translation keys: + +1. **Add the key in your code** using `t("your.new.key")` +2. **Add translation for that key in en-US.json file** +3. **Run the translation workflow:** + ```bash + pnpm i18n + ``` + This will: + - Generate translations for all languages using Lingo.dev + - Validate that all keys are present and used + +4. **Review and commit** the generated translation files + +### 4. Available Scripts + +```bash +# Generate translations using Lingo.dev +pnpm generate-translations + +# Scan and validate translation keys +pnpm scan-translations + +# Full workflow: generate + validate +pnpm i18n + +# Validate only (without generation) +pnpm i18n:validate +``` + +## Translation Key Validation + +### Automated Validation + +The project includes automated validation that runs: +- **Pre-commit hook**: Validates translations before allowing commits (when `LINGODOTDEV_API_KEY` is set) +- **GitHub Actions**: Validates translations on every PR and push to main + +### Validation Rules + +The validation script (`scan-translations.ts`) checks for: + +1. **Missing Keys**: Translation keys used in code but not present in translation files +2. **Unused Keys**: Translation keys present in translation files but not used in code +3. **Incomplete Translations**: Keys that exist in the default language (`en-US`) but are missing in target languages + +**What gets scanned:** +- All `.ts` and `.tsx` files in `apps/web/` +- Both `t()` function calls and `` components +- All locale files (`de-DE.json`, `fr-FR.json`, `ja-JP.json`, etc.) + +**What gets excluded:** +- Test files (`*.test.ts`, `*.test.tsx`, `*.spec.ts`, `*.spec.tsx`) +- Build directories (`node_modules`, `dist`, `build`, `.next`, `coverage`) +- Locale files themselves (from code scanning) + +**Note:** Test files are excluded because they often use mock or example translation keys for testing purposes that don't need to exist in production translation files. + +### Fixing Validation Errors + +#### Missing Keys + +If you encounter missing key errors: + +``` +❌ MISSING KEYS (2): + + These keys are used in code but not found in translation files: + + • auth.signup.email_required + • settings.profile.update_success +``` + +**Resolution:** +1. Ensure that translations for those keys are present in en-US.json . +2. Run `pnpm generate-translations` to have Lingo.dev generate the missing translations +3. OR manually add the keys to `apps/web/locales/en-US.json`: + ```json + { + "auth": { + "signup": { + "email_required": "Email is required" + } + }, + "settings": { + "profile": { + "update_success": "Profile updated successfully" + } + } + } + ``` +3. Run `pnpm scan-translations` to verify +4. Commit the changes + +#### Unused Keys + +If you encounter unused key errors: + +``` +⚠️ UNUSED KEYS (1): + + These keys exist in translation files but are not used in code: + + • old.deprecated.key +``` + +**Resolution:** +1. If the key is truly unused, remove it from all translation files +2. If the key should be used, add it to your code using `t("old.deprecated.key")` +3. Run `pnpm scan-translations` to verify +4. Commit the changes + +#### Incomplete Translations + +If you encounter incomplete translation errors: + +``` +⚠️ INCOMPLETE TRANSLATIONS: + + Some keys from en-US are missing in target languages: + + 📝 de-DE (5 missing keys): + • auth.new_feature.title + • auth.new_feature.description + • settings.advanced.option + ... and 2 more +``` + +**Resolution:** +1. **Recommended:** Run `pnpm generate-translations` to have Lingo.dev automatically translate the missing keys +2. **Manual:** Add the missing keys to the target language files: + ```bash + # Copy the structure from en-US.json and translate the values + # For example, in de-DE.json: + { + "auth": { + "new_feature": { + "title": "Neues Feature", + "description": "Beschreibung des neuen Features" + } + } + } + ``` +3. Run `pnpm scan-translations` to verify all translations are complete +4. Commit the changes + +## Pre-commit Hook Behavior + +The pre-commit hook will: + +1. Run `lint-staged` for code formatting +2. If `LINGODOTDEV_API_KEY` is set: + - Generate translations using Lingo.dev + - Validate translation keys + - Auto-add updated locale files to the commit + - **Block the commit** if validation fails +3. If `LINGODOTDEV_API_KEY` is not set: + - Skip translation validation (for community contributors) + - Show a warning message + +## Environment Variables + +### LINGODOTDEV_API_KEY + +This is the API key for Lingo.dev integration. + +**For Core Team:** +- Add to your local `.env` file +- Required for running translation generation + +**For Community Contributors:** +- Not required for local development +- Translation validation will be skipped +- The CI will still validate translations + +## Best Practices + +### 1. Keep Keys Organized + +Group related keys together: +```json +{ + "auth": { + "login": { ... }, + "signup": { ... }, + "forgot_password": { ... } + }, + "dashboard": { + "header": { ... }, + "sidebar": { ... } + } +} +``` + +### 2. Avoid Hardcoded Strings + +**❌ Bad:** +```tsx + +``` + +**✅ Good:** +```tsx + +``` + +### 3. Use Interpolation for Dynamic Content + +**❌ Bad:** +```tsx +{t("welcome")} {userName}! +``` + +**✅ Good:** +```tsx +{t("auth.welcome_message", { userName })} +``` + +With translation: +```json +{ + "auth": { + "welcome_message": "Welcome, {userName}!" + } +} +``` + +### 4. Avoid Dynamic Key Construction + +**❌ Bad:** +```tsx +const key = `errors.${errorCode}`; +t(key); +``` + +**✅ Good:** +```tsx +switch (errorCode) { + case "401": + return t("errors.unauthorized"); + case "404": + return t("errors.not_found"); + default: + return t("errors.unknown"); +} +``` + +### 5. Test Translation Keys + +When adding new features: +1. Add translation keys +2. Test in multiple languages using the language switcher +3. Ensure text doesn't overflow in longer translations (German, French) +4. Run `pnpm scan-translations` before committing + +## Troubleshooting + +### Issue: Pre-commit hook fails with validation errors + +**Solution:** +```bash +# Run the full i18n workflow +pnpm i18n + +# Fix any missing or unused keys +# Then commit again +git add . +git commit -m "your message" +``` + +### Issue: Translation validation passes locally but fails in CI + +**Solution:** +- Ensure all translation files are committed +- Check that `scan-translations.ts` hasn't been modified +- Verify that locale files are properly formatted JSON + +### Issue: Cannot commit because of missing translations + +**Solution:** +```bash +# If you have LINGODOTDEV_API_KEY: +pnpm generate-translations + +# If you don't have the API key (community contributor): +# Manually add the missing keys to en-US.json +# Then run validation: +pnpm scan-translations +``` + +### Issue: Getting "unused keys" for keys that are used + +**Solution:** +- The script scans `.ts` and `.tsx` files only +- If keys are used in other file types, they may be flagged +- Verify the key is actually used with `grep -r "your.key" apps/web/` +- If it's a false positive, consider updating the scanning patterns in `scan-translations.ts` + +## AI Assistant Guidelines + +When assisting with i18n-related tasks, always: + +1. **Use the `t()` function** for all user-facing text +2. **Follow key naming conventions** (lowercase, dots for nesting) +3. **Run validation** after making changes: `pnpm scan-translations` +4. **Fix missing keys** by adding them to `en-US.json` +5. **Remove unused keys** from all translation files +6. **Test the pre-commit hook** if making changes to translation workflow +7. **Update this rule file** if translation workflow changes + +### Fixing Missing Translation Keys + +When the AI encounters missing translation key errors: + +1. Identify the missing keys from the error output +2. Determine the appropriate section and naming for each key +3. Add the keys to `apps/web/locales/en-US.json` with meaningful English text +4. Ensure proper JSON structure and nesting +5. Run `pnpm scan-translations` to verify +6. Inform the user that other language files will be updated via Lingo.dev + +**Example:** +```typescript +// Error: Missing key "settings.api.rate_limit_exceeded" + +// Add to en-US.json: +{ + "settings": { + "api": { + "rate_limit_exceeded": "API rate limit exceeded. Please try again later." + } + } +} +``` + +### Removing Unused Translation Keys + +When the AI encounters unused translation key errors: + +1. Verify the keys are truly unused by searching the codebase +2. Remove the keys from `apps/web/locales/en-US.json` +3. Note that removal from other language files can be handled via Lingo.dev +4. Run `pnpm scan-translations` to verify + +## Migration Notes + +This project previously used Tolgee for translations. As of this migration: + +- **Old scripts**: `tolgee-pull` is deprecated (kept for reference) +- **New scripts**: Use `pnpm i18n` or `pnpm generate-translations` +- **Old workflows**: `tolgee.yml` and `tolgee-missing-key-check.yml` removed +- **New workflow**: `translation-check.yml` handles all validation + +--- + +**Last Updated:** October 14, 2025 +**Related Files:** +- `scan-translations.ts` - Translation validation script +- `.husky/pre-commit` - Pre-commit hook with i18n validation +- `.github/workflows/translation-check.yml` - CI workflow for translation validation +- `apps/web/locales/*.json` - Translation files diff --git a/.env.example b/.env.example index 8da4aad855..2441c96863 100644 --- a/.env.example +++ b/.env.example @@ -214,3 +214,7 @@ REDIS_URL=redis://localhost:6379 # AUDIT_LOG_ENABLED=0 # If the ip should be added in the log or not. Default 0 # AUDIT_LOG_GET_USER_IP=0 + + +# Lingo.dev API key for translation generation +LINGODOTDEV_API_KEY=your_api_key_here \ No newline at end of file diff --git a/.github/workflows/tolgee-missing-key-check.yml b/.github/workflows/tolgee-missing-key-check.yml deleted file mode 100644 index 7d5a4927ac..0000000000 --- a/.github/workflows/tolgee-missing-key-check.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Check Missing Translations - -permissions: - contents: read - -on: - workflow_dispatch: - pull_request_target: - types: [opened, synchronize, reopened] - -jobs: - check-missing-translations: - runs-on: ubuntu-latest - steps: - - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 - with: - egress-policy: audit - - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: - ref: ${{ github.event.pull_request.base.ref }} - - - name: Checkout PR - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Setup Node.js - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 - with: - node-version: 18 - - - name: Install Tolgee CLI - run: npm install -g @tolgee/cli - - - name: Compare Tolgee Keys - id: compare - run: | - tolgee compare --api-key ${{ secrets.TOLGEE_API_KEY }} > compare_output.txt - cat compare_output.txt - - - name: Check for Missing Translations - run: | - if grep -q "new key found" compare_output.txt; then - echo "New keys found that may require translations:" - exit 1 - else - echo "No new keys found." - fi diff --git a/.github/workflows/tolgee.yml b/.github/workflows/tolgee.yml deleted file mode 100644 index 7ac8b940ae..0000000000 --- a/.github/workflows/tolgee.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Tolgee Tagging on PR Merge -permissions: - contents: read - -on: - pull_request_target: - types: [closed] - branches: - - main - -jobs: - tag-production-keys: - name: Tag Production Keys - runs-on: ubuntu-latest - if: github.event.pull_request.merged == true - - steps: - - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 - with: - egress-policy: audit - - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 # This ensures we get the full git history - - - name: Get source branch name - id: branch-name - env: - RAW_BRANCH: ${{ github.head_ref }} - run: | - # Validate and sanitize branch name - only allow alphanumeric, dots, underscores, hyphens, and forward slashes - SOURCE_BRANCH=$(echo "$RAW_BRANCH" | sed 's/[^a-zA-Z0-9._\/-]//g') - - # Additional validation - ensure branch name is not empty after sanitization - if [[ -z "$SOURCE_BRANCH" ]]; then - echo "❌ Error: Branch name is empty after sanitization" - echo "Original branch: $RAW_BRANCH" - exit 1 - fi - - # Safely add to environment variables using GitHub's recommended method - # This prevents environment variable injection attacks - echo "SOURCE_BRANCH<> $GITHUB_ENV - echo "$SOURCE_BRANCH" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - echo "Detected source branch: $SOURCE_BRANCH" - - - name: Setup Node.js - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 - with: - node-version: 18 # Ensure compatibility with your project - - - name: Install Tolgee CLI - run: npm install -g @tolgee/cli - - - name: Tag Production Keys - run: | - npx tolgee tag \ - --api-key ${{ secrets.TOLGEE_API_KEY }} \ - --filter-extracted \ - --filter-tag "draft:${SOURCE_BRANCH}" \ - --tag production \ - --untag "draft:${SOURCE_BRANCH}" - - - name: Tag unused production keys as Deprecated - run: | - npx tolgee tag \ - --api-key ${{ secrets.TOLGEE_API_KEY }} \ - --filter-not-extracted --filter-tag production \ - --tag deprecated --untag production - - - name: Tag unused draft:current-branch keys as Deprecated - run: | - npx tolgee tag \ - --api-key ${{ secrets.TOLGEE_API_KEY }} \ - --filter-not-extracted --filter-tag "draft:${SOURCE_BRANCH}" \ - --tag deprecated --untag "draft:${SOURCE_BRANCH}" - - - name: Sync with backup - run: | - npx tolgee sync \ - --api-key ${{ secrets.TOLGEE_API_KEY }} \ - --backup ./tolgee-backup \ - --continue-on-warning \ - --yes - - - name: Upload backup as artifact - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 - with: - name: tolgee-backup-${{ github.sha }} - path: ./tolgee-backup - retention-days: 90 diff --git a/.github/workflows/translation-check.yml b/.github/workflows/translation-check.yml new file mode 100644 index 0000000000..c901eb5832 --- /dev/null +++ b/.github/workflows/translation-check.yml @@ -0,0 +1,63 @@ +name: Translation Validation + +permissions: + contents: read + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - "apps/web/**/*.ts" + - "apps/web/**/*.tsx" + - "apps/web/locales/**/*.json" + - "scan-translations.ts" + push: + branches: + - main + paths: + - "apps/web/**/*.ts" + - "apps/web/**/*.tsx" + - "apps/web/locales/**/*.json" + - "scan-translations.ts" + +jobs: + validate-translations: + name: Validate Translation Keys + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Node.js + uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 + with: + node-version: 18 + + - name: Setup pnpm + uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0 + with: + version: 9.15.9 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Validate translation keys + run: | + echo "" + echo "🔍 Validating translation keys..." + echo "" + pnpm run scan-translations + + - name: Summary + if: success() + run: | + echo "" + echo "✅ Translation validation completed successfully!" + echo "" diff --git a/.husky/pre-commit b/.husky/pre-commit index 7c3821438c..8371e9c20a 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -10,12 +10,34 @@ fi pnpm lint-staged -# Run tolgee-pull if branch.json exists and NEXT_PUBLIC_TOLGEE_API_KEY is not set -if [ -f branch.json ]; then - if [ -z "$NEXT_PUBLIC_TOLGEE_API_KEY" ]; then - echo "Skipping tolgee-pull: NEXT_PUBLIC_TOLGEE_API_KEY is not set" +# Run Lingo.dev i18n workflow if LINGODOTDEV_API_KEY is set +if [ -n "$LINGODOTDEV_API_KEY" ]; then + echo "" + echo "🌍 Running Lingo.dev translation workflow..." + echo "" + + # Run translation generation and validation + if pnpm run i18n; then + echo "" + echo "✅ Translation validation passed" + echo "" + # Add updated locale files to git + git add apps/web/locales/*.json else - pnpm run tolgee-pull - git add apps/web/locales + echo "" + echo "❌ Translation validation failed!" + echo "" + echo "Please fix the translation issues above before committing:" + echo " • Add missing translation keys to your locale files" + echo " • Remove unused translation keys" + echo "" + echo "Or run 'pnpm i18n' to see the detailed report" + echo "" + exit 1 fi +else + echo "" + echo "⚠️ Skipping translation validation: LINGODOTDEV_API_KEY is not set" + echo " (This is expected for community contributors)" + echo "" fi \ No newline at end of file diff --git a/.tolgeerc.json b/.tolgeerc.json deleted file mode 100644 index 4163248837..0000000000 --- a/.tolgeerc.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "$schema": "https://docs.tolgee.io/cli-schema.json", - "format": "JSON_TOLGEE", - "patterns": ["./apps/web/**/*.ts?(x)"], - "projectId": 10304, - "pull": { - "path": "./apps/web/locales" - }, - "push": { - "files": [ - { - "language": "en-US", - "path": "./apps/web/locales/en-US.json" - }, - { - "language": "de-DE", - "path": "./apps/web/locales/de-DE.json" - }, - { - "language": "fr-FR", - "path": "./apps/web/locales/fr-FR.json" - }, - { - "language": "pt-BR", - "path": "./apps/web/locales/pt-BR.json" - }, - { - "language": "zh-Hant-TW", - "path": "./apps/web/locales/zh-Hant-TW.json" - }, - { - "language": "pt-PT", - "path": "./apps/web/locales/pt-PT.json" - }, - { - "language": "ro-RO", - "path": "./apps/web/locales/ro-RO.json" - }, - { - "language": "ja-JP", - "path": "./apps/web/locales/ja-JP.json" - }, - { - "language": "zh-Hans-CN", - "path": "./apps/web/locales/zh-Hans-CN.json" - } - ], - "forceMode": "OVERRIDE" - }, - "strictNamespace": false -} diff --git a/.vscode/settings.json b/.vscode/settings.json index f5383fc66f..de352c7bc6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,10 @@ { "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], - "eslint.workingDirectories": [{ "mode": "auto" }], + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ], "javascript.updateImportsOnFileMove.enabled": "always", "sonarlint.connectedMode.project": { "connectionId": "formbricks", diff --git a/apps/storybook/.storybook/preview.ts b/apps/storybook/.storybook/preview.ts index 5d55cce53b..b0c7224444 100644 --- a/apps/storybook/.storybook/preview.ts +++ b/apps/storybook/.storybook/preview.ts @@ -1,29 +1,16 @@ import type { Preview } from "@storybook/react-vite"; -import { TolgeeProvider } from "@tolgee/react"; import React from "react"; -// Import translation data for Storybook -import enUSTranslations from "../../web/locales/en-US.json"; +import { I18nProvider } from "../../web/lingodotdev/client"; import "../../web/modules/ui/globals.css"; -import { TolgeeBase } from "../../web/tolgee/shared"; - -// Create a Storybook-specific Tolgee decorator -const withTolgee = (Story: any) => { - const tolgee = TolgeeBase().init({ - tagNewKeys: [], // No branch tagging in Storybook - }); +// Create a Storybook-specific Lingodot Dev decorator +const withLingodotDev = (Story: any) => { return React.createElement( - TolgeeProvider, + I18nProvider, { - tolgee, - fallback: "Loading", - ssr: { - language: "en-US", - staticData: { - "en-US": enUSTranslations, - }, - }, - }, + language: "en-US", + defaultLanguage: "en-US", + } as any, React.createElement(Story) ); }; @@ -37,7 +24,7 @@ const preview: Preview = { }, }, }, - decorators: [withTolgee], + decorators: [withLingodotDev], }; export default preview; diff --git a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks.tsx b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks.tsx index 6ea3a154e7..293224138e 100644 --- a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks.tsx +++ b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks.tsx @@ -1,9 +1,9 @@ "use client"; -import { useTranslate } from "@tolgee/react"; import { ArrowRight } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { TEnvironment } from "@formbricks/types/environment"; import { TProjectConfigChannel } from "@formbricks/types/project"; import { cn } from "@/lib/cn"; @@ -23,7 +23,7 @@ export const ConnectWithFormbricks = ({ appSetupCompleted, channel, }: ConnectWithFormbricksProps) => { - const { t } = useTranslate(); + const { t } = useTranslation(); const router = useRouter(); const handleFinishOnboarding = async () => { router.push(`/environments/${environment.id}/surveys`); diff --git a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.tsx b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.tsx index 404b10ce41..d5e705e501 100644 --- a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.tsx +++ b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.tsx @@ -1,10 +1,10 @@ "use client"; -import { useTranslate } from "@tolgee/react"; import Link from "next/link"; import "prismjs/themes/prism.css"; import { useState } from "react"; import toast from "react-hot-toast"; +import { useTranslation } from "react-i18next"; import { TProjectConfigChannel } from "@formbricks/types/project"; import { Button } from "@/modules/ui/components/button"; import { CodeBlock } from "@/modules/ui/components/code-block"; @@ -29,7 +29,7 @@ export const OnboardingSetupInstructions = ({ channel, appSetupCompleted, }: OnboardingSetupInstructionsProps) => { - const { t } = useTranslate(); + const { t } = useTranslation(); const [activeTab, setActiveTab] = useState(tabs[0].id); const htmlSnippetForAppSurveys = `