feat: refactor translation key management (#6717)

Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com>
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 <anshuman.pandey9999@gmail.com>
Co-authored-by: Matti Nannt <matti@formbricks.com>
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
Co-authored-by: Johannes <johannes@formbricks.com>
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
This commit is contained in:
Dhruwang Jariwala
2025-10-23 20:23:11 +05:30
committed by GitHub
parent c9a50a6ff2
commit a5fa876aa3
510 changed files with 6129 additions and 1883 deletions

View File

@@ -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 `<Trans>` component:
**Using the `t()` function:**
```tsx
import { useTranslate } from "@/lib/i18n/translate";
const MyComponent = () => {
const { t } = useTranslate();
return (
<div>
<h1>{t("common.welcome")}</h1>
<p>{t("pages.dashboard.description")}</p>
</div>
);
};
```
**Using the `<Trans>` component (for text with HTML elements):**
```tsx
import { Trans } from "react-i18next";
const MyComponent = () => {
return (
<div>
<p>
<Trans
i18nKey="auth.terms_agreement"
components={{
link: <a href="/terms" />,
b: <b />
}}
/>
</p>
</div>
);
};
```
**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 `<Trans i18nKey="">` 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
<button>Click here</button>
```
**✅ Good:**
```tsx
<button>{t("common.click_here")}</button>
```
### 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

View File

@@ -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

View File

@@ -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

View File

@@ -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<<EOF" >> $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

63
.github/workflows/translation-check.yml vendored Normal file
View File

@@ -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 ""

View File

@@ -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

View File

@@ -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
}

View File

@@ -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",

View File

@@ -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;

View File

@@ -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`);

View File

@@ -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 = `<!-- START Formbricks Surveys -->
<script type="text/javascript">

View File

@@ -4,9 +4,9 @@ import { ConnectWithFormbricks } from "@/app/(app)/(onboarding)/environments/[en
import { getEnvironment } from "@/lib/environment/service";
import { getPublicDomain } from "@/lib/getPublicUrl";
import { getProjectByEnvironmentId } from "@/lib/project/service";
import { getTranslate } from "@/lingodotdev/server";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
interface ConnectPageProps {
params: Promise<{

View File

@@ -1,10 +1,10 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { ActivityIcon, ShoppingCartIcon, SmileIcon, StarIcon, ThumbsUpIcon, UsersIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TProject } from "@formbricks/types/project";
import { TSurveyCreateInput } from "@formbricks/types/surveys/types";
import { TXMTemplate } from "@formbricks/types/templates";
@@ -23,7 +23,7 @@ interface XMTemplateListProps {
export const XMTemplateList = ({ project, user, environmentId }: XMTemplateListProps) => {
const [activeTemplateId, setActiveTemplateId] = useState<number | null>(null);
const { t } = useTranslate();
const { t } = useTranslation();
const router = useRouter();
const createSurvey = async (activeTemplate: TXMTemplate) => {

View File

@@ -1,6 +1,6 @@
import "@testing-library/jest-dom/vitest";
import { cleanup } from "@testing-library/preact";
import { TFnType } from "@tolgee/react";
import { TFunction } from "i18next";
import { afterEach, describe, expect, test, vi } from "vitest";
import { getXMSurveyDefault, getXMTemplates } from "./xm-templates";
@@ -14,7 +14,7 @@ describe("xm-templates", () => {
});
test("getXMSurveyDefault returns default survey template", () => {
const tMock = vi.fn((key) => key) as TFnType;
const tMock = vi.fn((key) => key) as TFunction;
const result = getXMSurveyDefault(tMock);
expect(result).toEqual({
@@ -29,7 +29,7 @@ describe("xm-templates", () => {
});
test("getXMTemplates returns all templates", () => {
const tMock = vi.fn((key) => key) as TFnType;
const tMock = vi.fn((key) => key) as TFunction;
const result = getXMTemplates(tMock);
expect(result).toHaveLength(6);
@@ -44,7 +44,7 @@ describe("xm-templates", () => {
test("getXMTemplates handles errors gracefully", async () => {
const tMock = vi.fn(() => {
throw new Error("Test error");
}) as TFnType;
}) as TFunction;
const result = getXMTemplates(tMock);

View File

@@ -1,5 +1,5 @@
import { createId } from "@paralleldrive/cuid2";
import { TFnType } from "@tolgee/react";
import { TFunction } from "i18next";
import { logger } from "@formbricks/logger";
import { TXMTemplate } from "@formbricks/types/templates";
import {
@@ -10,7 +10,7 @@ import {
getDefaultEndingCard,
} from "@/app/lib/survey-builder";
export const getXMSurveyDefault = (t: TFnType): TXMTemplate => {
export const getXMSurveyDefault = (t: TFunction): TXMTemplate => {
try {
return {
name: "",
@@ -26,7 +26,7 @@ export const getXMSurveyDefault = (t: TFnType): TXMTemplate => {
}
};
const npsSurvey = (t: TFnType): TXMTemplate => {
const npsSurvey = (t: TFunction): TXMTemplate => {
return {
...getXMSurveyDefault(t),
name: t("templates.nps_survey_name"),
@@ -55,7 +55,7 @@ const npsSurvey = (t: TFnType): TXMTemplate => {
};
};
const starRatingSurvey = (t: TFnType): TXMTemplate => {
const starRatingSurvey = (t: TFunction): TXMTemplate => {
const reusableQuestionIds = [createId(), createId(), createId()];
const defaultSurvey = getXMSurveyDefault(t);
@@ -153,7 +153,7 @@ const starRatingSurvey = (t: TFnType): TXMTemplate => {
};
};
const csatSurvey = (t: TFnType): TXMTemplate => {
const csatSurvey = (t: TFunction): TXMTemplate => {
const reusableQuestionIds = [createId(), createId(), createId()];
const defaultSurvey = getXMSurveyDefault(t);
@@ -247,7 +247,7 @@ const csatSurvey = (t: TFnType): TXMTemplate => {
};
};
const cessSurvey = (t: TFnType): TXMTemplate => {
const cessSurvey = (t: TFunction): TXMTemplate => {
return {
...getXMSurveyDefault(t),
name: t("templates.cess_survey_name"),
@@ -272,7 +272,7 @@ const cessSurvey = (t: TFnType): TXMTemplate => {
};
};
const smileysRatingSurvey = (t: TFnType): TXMTemplate => {
const smileysRatingSurvey = (t: TFunction): TXMTemplate => {
const reusableQuestionIds = [createId(), createId(), createId()];
const defaultSurvey = getXMSurveyDefault(t);
@@ -370,7 +370,7 @@ const smileysRatingSurvey = (t: TFnType): TXMTemplate => {
};
};
const enpsSurvey = (t: TFnType): TXMTemplate => {
const enpsSurvey = (t: TFunction): TXMTemplate => {
return {
...getXMSurveyDefault(t),
name: t("templates.enps_survey_name"),
@@ -399,7 +399,7 @@ const enpsSurvey = (t: TFnType): TXMTemplate => {
};
};
export const getXMTemplates = (t: TFnType): TXMTemplate[] => {
export const getXMTemplates = (t: TFunction): TXMTemplate[] => {
try {
return [
npsSurvey(t),

View File

@@ -6,10 +6,10 @@ import { getEnvironment } from "@/lib/environment/service";
import { getProjectByEnvironmentId, getUserProjects } from "@/lib/project/service";
import { getUser } from "@/lib/user/service";
import { getOrganizationIdFromEnvironmentId } from "@/lib/utils/helper";
import { getTranslate } from "@/lingodotdev/server";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
interface XMTemplatePageProps {
params: Promise<{

View File

@@ -1,10 +1,10 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { ArrowUpRightIcon, ChevronRightIcon, LogOutIcon } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { TOrganization } from "@formbricks/types/organizations";
import { TUser } from "@formbricks/types/user";
import FBLogo from "@/images/formbricks-wordmark.svg";
@@ -27,7 +27,7 @@ interface LandingSidebarProps {
export const LandingSidebar = ({ user, organization }: LandingSidebarProps) => {
const [openCreateOrganizationModal, setOpenCreateOrganizationModal] = useState<boolean>(false);
const { t } = useTranslate();
const { t } = useTranslation();
const { signOut: signOutWithAudit } = useSignOut({ id: user.id, email: user.email });
const dropdownNavigation = [

View File

@@ -6,10 +6,10 @@ import { getMembershipByUserIdOrganizationId } from "@/lib/membership/service";
import { getAccessFlags } from "@/lib/membership/utils";
import { getOrganizationsByUserId } from "@/lib/organization/service";
import { getUser } from "@/lib/user/service";
import { getTranslate } from "@/lingodotdev/server";
import { getIsMultiOrgEnabled } from "@/modules/ee/license-check/lib/utils";
import { getOrganizationAuth } from "@/modules/organization/lib/utils";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
const Page = async (props) => {
const params = await props.params;

View File

@@ -6,9 +6,9 @@ import { IS_POSTHOG_CONFIGURED } from "@/lib/constants";
import { canUserAccessOrganization } from "@/lib/organization/auth";
import { getOrganization } from "@/lib/organization/service";
import { getUser } from "@/lib/user/service";
import { getTranslate } from "@/lingodotdev/server";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { ToasterClient } from "@/modules/ui/components/toaster-client";
import { getTranslate } from "@/tolgee/server";
const ProjectOnboardingLayout = async (props) => {
const params = await props.params;

View File

@@ -3,10 +3,10 @@ import Link from "next/link";
import { redirect } from "next/navigation";
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
import { getUserProjects } from "@/lib/project/service";
import { getTranslate } from "@/lingodotdev/server";
import { getOrganizationAuth } from "@/modules/organization/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
interface ChannelPageProps {
params: Promise<{

View File

@@ -4,9 +4,9 @@ import { getMembershipByUserIdOrganizationId } from "@/lib/membership/service";
import { getAccessFlags } from "@/lib/membership/utils";
import { getOrganization } from "@/lib/organization/service";
import { getOrganizationProjectsCount } from "@/lib/project/service";
import { getTranslate } from "@/lingodotdev/server";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getOrganizationProjectsLimit } from "@/modules/ee/license-check/lib/utils";
import { getTranslate } from "@/tolgee/server";
const OnboardingLayout = async (props) => {
const params = await props.params;

View File

@@ -3,10 +3,10 @@ import Link from "next/link";
import { redirect } from "next/navigation";
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
import { getUserProjects } from "@/lib/project/service";
import { getTranslate } from "@/lingodotdev/server";
import { getOrganizationAuth } from "@/modules/organization/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
interface ModePageProps {
params: Promise<{

View File

@@ -1,12 +1,12 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslate } from "@tolgee/react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import {
TProjectConfigChannel,
TProjectConfigIndustry,
@@ -59,7 +59,7 @@ export const ProjectSettings = ({
const [createTeamModalOpen, setCreateTeamModalOpen] = useState(false);
const router = useRouter();
const { t } = useTranslate();
const { t } = useTranslation();
const addProject = async (data: TProjectUpdateInput) => {
try {
const createProjectResponse = await createProjectAction({

View File

@@ -6,11 +6,11 @@ import { getTeamsByOrganizationId } from "@/app/(app)/(onboarding)/lib/onboardin
import { ProjectSettings } from "@/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/settings/components/ProjectSettings";
import { DEFAULT_BRAND_COLOR } from "@/lib/constants";
import { getUserProjects } from "@/lib/project/service";
import { getTranslate } from "@/lingodotdev/server";
import { getAccessControlPermission } from "@/modules/ee/license-check/lib/utils";
import { getOrganizationAuth } from "@/modules/organization/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
interface ProjectSettingsPageProps {
params: Promise<{

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/modules/ui/components/button";
import { Confetti } from "@/modules/ui/components/confetti";
@@ -11,7 +11,7 @@ interface ConfirmationPageProps {
}
export const ConfirmationPage = ({ environmentId }: ConfirmationPageProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [showConfetti, setShowConfetti] = useState(false);
useEffect(() => {
setShowConfetti(true);

View File

@@ -13,6 +13,7 @@ import {
getOrganizationByEnvironmentId,
} from "@/lib/organization/service";
import { getUser } from "@/lib/user/service";
import { getTranslate } from "@/lingodotdev/server";
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/license";
import {
getAccessControlPermission,
@@ -21,7 +22,6 @@ import {
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { LimitsReachedBanner } from "@/modules/ui/components/limits-reached-banner";
import { PendingDowngradeBanner } from "@/modules/ui/components/pending-downgrade-banner";
import { getTranslate } from "@/tolgee/server";
interface EnvironmentLayoutProps {
environmentId: string;

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { cn } from "@/lib/cn";
import { Label } from "@/modules/ui/components/label";
@@ -14,7 +14,7 @@ interface EnvironmentSwitchProps {
}
export const EnvironmentSwitch = ({ environment, environments }: EnvironmentSwitchProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const router = useRouter();
const [isEnvSwitchChecked, setIsEnvSwitchChecked] = useState(environment?.type === "development");
const [isLoading, setIsLoading] = useState(false);

View File

@@ -1,6 +1,5 @@
"use client";
import { useTranslate } from "@tolgee/react";
import {
ArrowUpRightIcon,
ChevronRightIcon,
@@ -17,6 +16,7 @@ import Image from "next/image";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TOrganizationRole } from "@formbricks/types/memberships";
import { TOrganization } from "@formbricks/types/organizations";
@@ -59,7 +59,7 @@ export const MainNavigation = ({
}: NavigationProps) => {
const router = useRouter();
const pathname = usePathname();
const { t } = useTranslate();
const { t } = useTranslation();
const [isCollapsed, setIsCollapsed] = useState(false);
const [isTextVisible, setIsTextVisible] = useState(true);
const [latestVersion, setLatestVersion] = useState("");

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { AlertTriangleIcon, CheckIcon, RotateCcwIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { cn } from "@/lib/cn";
import { Button } from "@/modules/ui/components/button";
@@ -12,7 +12,7 @@ interface WidgetStatusIndicatorProps {
}
export const WidgetStatusIndicator = ({ environment }: WidgetStatusIndicatorProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const router = useRouter();
const stati = {
notImplemented: {

View File

@@ -1,9 +1,9 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { ChevronDownIcon, CircleHelpIcon, Code2Icon, Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { BreadcrumbItem } from "@/modules/ui/components/breadcrumb";
import {
DropdownMenu,
@@ -21,7 +21,7 @@ export const EnvironmentBreadcrumb = ({
environments: { id: string; type: string }[];
currentEnvironment: { id: string; type: string };
}) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [isEnvironmentDropdownOpen, setIsEnvironmentDropdownOpen] = useState(false);
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);

View File

@@ -1,7 +1,6 @@
"use client";
import * as Sentry from "@sentry/nextjs";
import { useTranslate } from "@tolgee/react";
import {
BuildingIcon,
ChevronDownIcon,
@@ -12,6 +11,7 @@ import {
} from "lucide-react";
import { usePathname, useRouter } from "next/navigation";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { logger } from "@formbricks/logger";
import { CreateOrganizationModal } from "@/modules/organization/components/CreateOrganizationModal";
import { BreadcrumbItem } from "@/modules/ui/components/breadcrumb";
@@ -43,7 +43,7 @@ export const OrganizationBreadcrumb = ({
isMember,
isOwnerOrManager,
}: OrganizationBreadcrumbProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [isOrganizationDropdownOpen, setIsOrganizationDropdownOpen] = useState(false);
const [openCreateOrganizationModal, setOpenCreateOrganizationModal] = useState(false);
const pathname = usePathname();

View File

@@ -1,10 +1,10 @@
"use client";
import * as Sentry from "@sentry/nextjs";
import { useTranslate } from "@tolgee/react";
import { ChevronDownIcon, ChevronRightIcon, CogIcon, FolderOpenIcon, Loader2, PlusIcon } from "lucide-react";
import { usePathname, useRouter } from "next/navigation";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { logger } from "@formbricks/logger";
import { CreateProjectModal } from "@/modules/projects/components/create-project-modal";
import { ProjectLimitModal } from "@/modules/projects/components/project-limit-modal";
@@ -44,7 +44,7 @@ export const ProjectBreadcrumb = ({
isAccessControlAllowed,
isEnvironmentBreadcrumbVisible,
}: ProjectBreadcrumbProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [isProjectDropdownOpen, setIsProjectDropdownOpen] = useState(false);
const [openCreateProjectModal, setOpenCreateProjectModal] = useState(false);
const [openLimitModal, setOpenLimitModal] = useState(false);

View File

@@ -1,11 +1,12 @@
"use client";
import { TFnType, useTranslate } from "@tolgee/react";
import { TFunction } from "i18next";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { Control, Controller, useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TIntegrationItem } from "@formbricks/types/integration";
import {
TIntegrationAirtable,
@@ -58,7 +59,7 @@ type AddIntegrationModalProps = {
} & EditModeProps;
const NoBaseFoundError = () => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<Alert>
<AlertTitle>{t("environments.integrations.airtable.no_bases_found")}</AlertTitle>
@@ -80,7 +81,7 @@ const renderQuestionSelection = ({
includeCreatedAt,
setIncludeCreatedAt,
}: {
t: TFnType;
t: TFunction;
selectedSurvey: TSurvey;
control: Control<IntegrationModalInputs>;
includeVariables: boolean;
@@ -153,7 +154,7 @@ export const AddIntegrationModal = ({
isEditMode,
defaultData,
}: AddIntegrationModalProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const router = useRouter();
const [tables, setTables] = useState<TIntegrationAirtableTables["tables"]>([]);
const [isLoading, setIsLoading] = useState(false);

View File

@@ -1,7 +1,7 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { Control, Controller, UseFormSetValue } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { TIntegrationItem } from "@formbricks/types/integration";
import { Label } from "@/modules/ui/components/label";
import {
@@ -30,7 +30,7 @@ export const BaseSelectDropdown = ({
setValue,
defaultValue,
}: BaseSelectProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div className="flex w-full flex-col">
<Label htmlFor="base">{t("environments.integrations.airtable.airtable_base")}</Label>

View File

@@ -1,9 +1,9 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { Trash2Icon } from "lucide-react";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TIntegrationItem } from "@formbricks/types/integration";
import { TIntegrationAirtable } from "@formbricks/types/integration/airtable";
@@ -30,7 +30,7 @@ interface ManageIntegrationProps {
export const ManageIntegration = (props: ManageIntegrationProps) => {
const { airtableIntegration, environment, environmentId, setIsConnected, surveys, airtableArray } = props;
const { t } = useTranslate();
const { t } = useTranslation();
const tableHeaders = [
t("common.survey"),

View File

@@ -7,11 +7,11 @@ import { getAirtableTables } from "@/lib/airtable/service";
import { AIRTABLE_CLIENT_ID, WEBAPP_URL } from "@/lib/constants";
import { getIntegrations } from "@/lib/integration/service";
import { findMatchingLocale } from "@/lib/utils/locale";
import { getTranslate } from "@/lingodotdev/server";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
const Page = async (props) => {
const params = await props.params;

View File

@@ -1,10 +1,10 @@
"use client";
import { useTranslate } from "@tolgee/react";
import Image from "next/image";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import {
TIntegrationGoogleSheets,
TIntegrationGoogleSheetsConfigData,
@@ -56,7 +56,7 @@ export const AddIntegrationModal = ({
googleSheetIntegration,
selectedIntegration,
}: AddIntegrationModalProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const integrationData: TIntegrationGoogleSheetsConfigData = {
spreadsheetId: "",
spreadsheetName: "",

View File

@@ -1,9 +1,9 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { Trash2Icon } from "lucide-react";
import { useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import {
TIntegrationGoogleSheets,
@@ -34,7 +34,7 @@ export const ManageIntegration = ({
setSelectedIntegration,
locale,
}: ManageIntegrationProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [isDeleteIntegrationModalOpen, setIsDeleteIntegrationModalOpen] = useState(false);
let integrationArray: TIntegrationGoogleSheetsConfigData[] = [];
if (googleSheetIntegration?.config.data) {

View File

@@ -1,11 +1,11 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import { Button } from "@/modules/ui/components/button";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
const Loading = () => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div className="mt-6 p-6">
<GoBackButton />

View File

@@ -10,11 +10,11 @@ import {
} from "@/lib/constants";
import { getIntegrations } from "@/lib/integration/service";
import { findMatchingLocale } from "@/lib/utils/locale";
import { getTranslate } from "@/lingodotdev/server";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
const Page = async (props) => {
const params = await props.params;

View File

@@ -1,11 +1,11 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { PlusIcon, TrashIcon } from "lucide-react";
import Image from "next/image";
import React, { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TIntegrationInput } from "@formbricks/types/integration";
import {
TIntegrationNotion,
@@ -56,7 +56,7 @@ export const AddIntegrationModal = ({
databases,
selectedIntegration,
}: AddIntegrationModalProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const { handleSubmit } = useForm();
const [selectedDatabase, setSelectedDatabase] = useState<TIntegrationNotionDatabase | null>();
const [selectedSurvey, setSelectedSurvey] = useState<TSurvey | null>(null);

View File

@@ -1,9 +1,9 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { RefreshCcwIcon, Trash2Icon } from "lucide-react";
import React, { useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TIntegrationNotion, TIntegrationNotionConfigData } from "@formbricks/types/integration/notion";
import { TUserLocale } from "@formbricks/types/user";
@@ -36,7 +36,7 @@ export const ManageIntegration = ({
locale,
handleNotionAuthorization,
}: ManageIntegrationProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [isDeleteIntegrationModalOpen, setIsDeleteIntegrationModalOpen] = useState(false);
const [isDeleting, setisDeleting] = useState(false);

View File

@@ -1,11 +1,11 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import { Button } from "@/modules/ui/components/button";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
const Loading = () => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div className="mt-6 p-6">
<GoBackButton />

View File

@@ -12,11 +12,11 @@ import {
import { getIntegrationByType } from "@/lib/integration/service";
import { getNotionDatabases } from "@/lib/notion/service";
import { findMatchingLocale } from "@/lib/utils/locale";
import { getTranslate } from "@/lingodotdev/server";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
const Page = async (props) => {
const params = await props.params;

View File

@@ -1,4 +1,4 @@
import { TFnType } from "@tolgee/react";
import { TFunction } from "i18next";
import Image from "next/image";
import { redirect } from "next/navigation";
import { TIntegrationType } from "@formbricks/types/integration";
@@ -14,14 +14,14 @@ import SlackLogo from "@/images/slacklogo.png";
import WebhookLogo from "@/images/webhook.png";
import ZapierLogo from "@/images/zapier-small.png";
import { getIntegrations } from "@/lib/integration/service";
import { getTranslate } from "@/lingodotdev/server";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
import { Card } from "@/modules/ui/components/integration-card";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
const getStatusText = (count: number, t: TFnType, type: string) => {
const getStatusText = (count: number, t: TFunction, type: string) => {
if (count === 1) return `1 ${type}`;
if (count === 0) return t("common.not_connected");
return `${count} ${type}s`;

View File

@@ -1,12 +1,12 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { CircleHelpIcon } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TIntegrationItem } from "@formbricks/types/integration";
import {
TIntegrationSlack,
@@ -54,7 +54,7 @@ export const AddChannelMappingModal = ({
selectedIntegration,
}: AddChannelMappingModalProps) => {
const { handleSubmit } = useForm();
const { t } = useTranslate();
const { t } = useTranslation();
const [selectedQuestions, setSelectedQuestions] = useState<string[]>([]);
const [isLinkingChannel, setIsLinkingChannel] = useState(false);
const [selectedSurvey, setSelectedSurvey] = useState<TSurvey | null>(null);

View File

@@ -1,9 +1,9 @@
"use client";
import { T, useTranslate } from "@tolgee/react";
import { Trash2Icon } from "lucide-react";
import React, { useState } from "react";
import toast from "react-hot-toast";
import { Trans, useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TIntegrationSlack, TIntegrationSlackConfigData } from "@formbricks/types/integration/slack";
import { TUserLocale } from "@formbricks/types/user";
@@ -39,7 +39,7 @@ export const ManageIntegration = ({
handleSlackAuthorization,
locale,
}: ManageIntegrationProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [isDeleteIntegrationModalOpen, setIsDeleteIntegrationModalOpen] = useState(false);
const [isDeleting, setisDeleting] = useState(false);
let integrationArray: TIntegrationSlackConfigData[] = [];
@@ -76,9 +76,9 @@ export const ManageIntegration = ({
{showReconnectButton && (
<div className="mb-4 flex w-full items-center justify-between space-x-4">
<p className="text-amber-700">
<T
keyName="environments.integrations.slack.slack_reconnect_button_description"
params={{ b: <b /> }}
<Trans
i18nKey="environments.integrations.slack.slack_reconnect_button_description"
components={{ b: <b /> }}
/>
</p>
<Button onClick={handleSlackAuthorization} variant="secondary">

View File

@@ -5,11 +5,11 @@ import { SlackWrapper } from "@/app/(app)/environments/[environmentId]/project/i
import { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, WEBAPP_URL } from "@/lib/constants";
import { getIntegrationByType } from "@/lib/integration/service";
import { findMatchingLocale } from "@/lib/utils/locale";
import { getTranslate } from "@/lingodotdev/server";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
const Page = async (props) => {
const params = await props.params;

View File

@@ -1,7 +1,7 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { usePathname } from "next/navigation";
import { useTranslation } from "react-i18next";
import { SecondaryNavigation } from "@/modules/ui/components/secondary-navigation";
interface AccountSettingsNavbarProps {
@@ -12,7 +12,7 @@ interface AccountSettingsNavbarProps {
export const AccountSettingsNavbar = ({ environmentId, activeId, loading }: AccountSettingsNavbarProps) => {
const pathname = usePathname();
const { t } = useTranslate();
const { t } = useTranslation();
const navigation = [
{
id: "profile",

View File

@@ -1,8 +1,8 @@
import { getServerSession } from "next-auth";
import { getOrganizationByEnvironmentId } from "@/lib/organization/service";
import { getProjectByEnvironmentId } from "@/lib/project/service";
import { getTranslate } from "@/lingodotdev/server";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getTranslate } from "@/tolgee/server";
const AccountSettingsLayout = async (props) => {
const params = await props.params;

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { HelpCircleIcon, UsersIcon } from "lucide-react";
import Link from "next/link";
import { useTranslation } from "react-i18next";
import { TUser } from "@formbricks/types/user";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/modules/ui/components/tooltip";
import { Membership } from "../types";
@@ -23,7 +23,7 @@ export const EditAlerts = ({
autoDisableNotificationType,
autoDisableNotificationElementId,
}: EditAlertsProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<>
{memberships.map((membership) => (

View File

@@ -1,6 +1,6 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import { SlackIcon } from "@/modules/ui/components/icons";
interface IntegrationsTipProps {
@@ -8,7 +8,7 @@ interface IntegrationsTipProps {
}
export const IntegrationsTip = ({ environmentId }: IntegrationsTipProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div>
<div className="flex max-w-4xl items-center space-y-3 rounded-lg border border-blue-100 bg-blue-50 p-4 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base">

View File

@@ -1,9 +1,9 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TUserNotificationSettings } from "@formbricks/types/user";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Switch } from "@/modules/ui/components/switch";
@@ -25,7 +25,7 @@ export const NotificationSwitch = ({
autoDisableNotificationElementId,
}: NotificationSwitchProps) => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslate();
const { t } = useTranslation();
const router = useRouter();
const isChecked =
notificationType === "unsubscribedOrganizationIds"

View File

@@ -1,13 +1,13 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import { LoadingCard } from "@/app/(app)/components/LoadingCard";
import { AccountSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(account)/components/AccountSettingsNavbar";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
const Loading = () => {
const { t } = useTranslate();
const { t } = useTranslation();
const cards = [
{
title: t("environments.settings.notifications.email_alerts_surveys"),

View File

@@ -4,10 +4,10 @@ import { TUserNotificationSettings } from "@formbricks/types/user";
import { AccountSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(account)/components/AccountSettingsNavbar";
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { getUser } from "@/lib/user/service";
import { getTranslate } from "@/lingodotdev/server";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
import { EditAlerts } from "./components/EditAlerts";
import { IntegrationsTip } from "./components/IntegrationsTip";
import type { Membership } from "./types";

View File

@@ -1,7 +1,7 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { TUser } from "@formbricks/types/user";
import { DisableTwoFactorModal } from "@/modules/ee/two-factor-auth/components/disable-two-factor-modal";
import { EnableTwoFactorModal } from "@/modules/ee/two-factor-auth/components/enable-two-factor-modal";
@@ -12,7 +12,7 @@ interface AccountSecurityProps {
}
export const AccountSecurity = ({ user }: AccountSecurityProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [twoFactorModalOpen, setTwoFactorModalOpen] = useState(false);
const [disableTwoFactorModalOpen, setDisableTwoFactorModalOpen] = useState(false);

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import type { Session } from "next-auth";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { TOrganization } from "@formbricks/types/organizations";
import { TUser } from "@formbricks/types/user";
import { DeleteAccountModal } from "@/modules/account/components/DeleteAccountModal";
@@ -24,7 +24,7 @@ export const DeleteAccount = ({
}) => {
const [isModalOpen, setModalOpen] = useState(false);
const isDeleteDisabled = !isMultiOrgEnabled && organizationsWithSingleOwner.length > 0;
const { t } = useTranslate();
const { t } = useTranslation();
if (!session) {
return null;
}

View File

@@ -1,11 +1,11 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslate } from "@tolgee/react";
import { ChevronDownIcon } from "lucide-react";
import { useState } from "react";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { TUser, TUserUpdateInput, ZUser, ZUserEmail } from "@formbricks/types/user";
import { PasswordConfirmationModal } from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/components/password-confirmation-modal";
@@ -42,7 +42,7 @@ export const EditProfileDetailsForm = ({
isPasswordResetEnabled,
emailVerificationDisabled,
}: IEditProfileDetailsFormProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const form = useForm<TEditProfileNameForm>({
defaultValues: {

View File

@@ -1,8 +1,8 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslate } from "@tolgee/react";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { ZUserPassword } from "@formbricks/types/user";
import { Button } from "@/modules/ui/components/button";
@@ -39,7 +39,7 @@ export const PasswordConfirmationModal = ({
newEmail,
onConfirm,
}: PasswordConfirmationModalProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const form = useForm<FormValues>({
resolver: zodResolver(PasswordConfirmationSchema),

View File

@@ -1,13 +1,13 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import { LoadingCard } from "@/app/(app)/components/LoadingCard";
import { AccountSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(account)/components/AccountSettingsNavbar";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
const Loading = () => {
const { t } = useTranslate();
const { t } = useTranslation();
const cards = [
{
title: t("environments.settings.profile.personal_information"),

View File

@@ -3,13 +3,13 @@ import { AccountSecurity } from "@/app/(app)/environments/[environmentId]/settin
import { EMAIL_VERIFICATION_DISABLED, IS_FORMBRICKS_CLOUD, PASSWORD_RESET_DISABLED } from "@/lib/constants";
import { getOrganizationsWhereUserIsSingleOwner } from "@/lib/organization/service";
import { getUser } from "@/lib/user/service";
import { getTranslate } from "@/lingodotdev/server";
import { getIsMultiOrgEnabled, getIsTwoFactorAuthEnabled } from "@/modules/ee/license-check/lib/utils";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
import { IdBadge } from "@/modules/ui/components/id-badge";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { UpgradePrompt } from "@/modules/ui/components/upgrade-prompt";
import { getTranslate } from "@/tolgee/server";
import { SettingsCard } from "../../components/SettingsCard";
import { DeleteAccount } from "./components/DeleteAccount";
import { EditProfileDetailsForm } from "./components/EditProfileDetailsForm";

View File

@@ -1,8 +1,8 @@
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
import { getTranslate } from "@/lingodotdev/server";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
const Loading = async () => {
const t = await getTranslate();

View File

@@ -1,7 +1,7 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { usePathname } from "next/navigation";
import { useTranslation } from "react-i18next";
import { TOrganizationRole } from "@formbricks/types/memberships";
import { getAccessFlags } from "@/lib/membership/utils";
import { SecondaryNavigation } from "@/modules/ui/components/secondary-navigation";
@@ -24,7 +24,7 @@ export const OrganizationSettingsNavbar = ({
const pathname = usePathname();
const { isMember, isOwner } = getAccessFlags(membershipRole);
const isPricingDisabled = isMember;
const { t } = useTranslate();
const { t } = useTranslation();
const navigation = [
{

View File

@@ -1,8 +1,8 @@
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
import { getTranslate } from "@/lingodotdev/server";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
const Loading = async () => {
const t = await getTranslate();

View File

@@ -3,12 +3,12 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
import { getTranslate } from "@/lingodotdev/server";
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/license";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
const Page = async (props) => {
const params = await props.params;

View File

@@ -1,9 +1,9 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useRouter } from "next/navigation";
import { Dispatch, SetStateAction, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TOrganization } from "@formbricks/types/organizations";
import { deleteOrganizationAction } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/actions";
import { FORMBRICKS_ENVIRONMENT_ID_LS } from "@/lib/localStorage";
@@ -25,7 +25,7 @@ export const DeleteOrganization = ({
}: DeleteOrganizationProps) => {
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const { t } = useTranslate();
const { t } = useTranslation();
const router = useRouter();
const handleDeleteOrganization = async () => {
@@ -100,7 +100,7 @@ const DeleteOrganizationModal = ({
isDeleting,
}: DeleteOrganizationModalProps) => {
const [inputValue, setInputValue] = useState("");
const { t } = useTranslate();
const { t } = useTranslation();
const handleInputChange = (e) => {
setInputValue(e.target.value);
};

View File

@@ -1,9 +1,9 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslate } from "@tolgee/react";
import { SubmitHandler, useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { TOrganizationRole } from "@formbricks/types/memberships";
import { TOrganization, ZOrganization } from "@formbricks/types/organizations";
@@ -32,7 +32,7 @@ const ZEditOrganizationNameFormSchema = ZOrganization.pick({ name: true });
type EditOrganizationNameForm = z.infer<typeof ZEditOrganizationNameFormSchema>;
export const EditOrganizationNameForm = ({ organization, membershipRole }: EditOrganizationNameProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const form = useForm<EditOrganizationNameForm>({
defaultValues: {
name: organization.name,

View File

@@ -1,9 +1,9 @@
import { LoadingCard } from "@/app/(app)/components/LoadingCard";
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
import { getTranslate } from "@/lingodotdev/server";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
const Loading = async () => {
const t = await getTranslate();

View File

@@ -1,6 +1,7 @@
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
import { FB_LOGO_URL, IS_FORMBRICKS_CLOUD, IS_STORAGE_CONFIGURED } from "@/lib/constants";
import { getUser } from "@/lib/user/service";
import { getTranslate } from "@/lingodotdev/server";
import { getIsMultiOrgEnabled, getWhiteLabelPermission } from "@/modules/ee/license-check/lib/utils";
import { EmailCustomizationSettings } from "@/modules/ee/whitelabel/email-customization/components/email-customization-settings";
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
@@ -8,7 +9,6 @@ import { Alert, AlertDescription } from "@/modules/ui/components/alert";
import { IdBadge } from "@/modules/ui/components/id-badge";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
import { SettingsCard } from "../../components/SettingsCard";
import { DeleteOrganization } from "./components/DeleteOrganization";
import { EditOrganizationNameForm } from "./components/EditOrganizationNameForm";

View File

@@ -1,8 +1,8 @@
import { getServerSession } from "next-auth";
import { getOrganizationByEnvironmentId } from "@/lib/organization/service";
import { getProjectByEnvironmentId } from "@/lib/project/service";
import { getTranslate } from "@/lingodotdev/server";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getTranslate } from "@/tolgee/server";
const Layout = async (props) => {
const params = await props.params;

View File

@@ -1,6 +1,6 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/cn";
import { Badge } from "@/modules/ui/components/badge";
import { Button } from "@/modules/ui/components/button";
@@ -31,7 +31,7 @@ export const SettingsCard = ({
className?: string;
buttonInfo?: ButtonInfo;
}) => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div
className={cn(

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { Unplug } from "lucide-react";
import Link from "next/link";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { Button } from "@/modules/ui/components/button";
@@ -11,7 +11,7 @@ interface TEmptyAppSurveysProps {
}
export const EmptyAppSurveys = ({ environment }: TEmptyAppSurveysProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div className="flex w-full items-center justify-center gap-8 bg-slate-100 py-12">
<div className="flex h-20 w-20 items-center justify-center rounded-full border border-slate-200 bg-white">

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { InboxIcon, PresentationIcon } from "lucide-react";
import { usePathname } from "next/navigation";
import { useTranslation } from "react-i18next";
import { TSurvey } from "@formbricks/types/surveys/types";
import { revalidateSurveyIdPath } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions";
import { SecondaryNavigation } from "@/modules/ui/components/secondary-navigation";
@@ -19,7 +19,7 @@ export const SurveyAnalysisNavigation = ({
activeId,
}: SurveyAnalysisNavigationProps) => {
const pathname = usePathname();
const { t } = useTranslate();
const { t } = useTranslation();
const url = `/environments/${environmentId}/surveys/${survey.id}`;

View File

@@ -1,7 +1,8 @@
"use client";
import { TFnType, useTranslate } from "@tolgee/react";
import { TFunction } from "i18next";
import React from "react";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurveyQuota } from "@formbricks/types/quota";
import { TResponseDataValue, TResponseTableData, TResponseWithQuotas } from "@formbricks/types/responses";
@@ -83,7 +84,7 @@ export const extractResponseData = (response: TResponseWithQuotas, survey: TSurv
export const mapResponsesToTableData = (
responses: TResponseWithQuotas[],
survey: TSurvey,
t: TFnType
t: TFunction
): TResponseTableData[] => {
return responses.map((response) => ({
responseData: extractResponseData(response, survey),
@@ -124,7 +125,7 @@ export const ResponseDataView: React.FC<ResponseDataViewProps> = ({
isQuotasAllowed,
quotas,
}) => {
const { t } = useTranslate();
const { t } = useTranslation();
const data = mapResponsesToTableData(responses, survey, t);
return (

View File

@@ -15,9 +15,9 @@ import { SortableContext, arrayMove, horizontalListSortingStrategy } from "@dnd-
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Sentry from "@sentry/nextjs";
import { VisibilityState, getCoreRowModel, useReactTable } from "@tanstack/react-table";
import { useTranslate } from "@tolgee/react";
import { useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurveyQuota } from "@formbricks/types/quota";
import { TResponseTableData, TResponseWithQuotas } from "@formbricks/types/responses";
@@ -74,7 +74,7 @@ export const ResponseTable = ({
isQuotasAllowed,
quotas,
}: ResponseTableProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const [rowSelection, setRowSelection] = useState({});
const [isTableSettingsModalOpen, setIsTableSettingsModalOpen] = useState(false);

View File

@@ -1,7 +1,7 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import { TFnType } from "@tolgee/react";
import { TFunction } from "i18next";
import { CircleHelpIcon, EyeOffIcon, MailIcon, TagIcon } from "lucide-react";
import Link from "next/link";
import { TResponseTableData } from "@formbricks/types/responses";
@@ -32,7 +32,7 @@ const getQuestionColumnsData = (
question: TSurveyQuestion,
survey: TSurvey,
isExpanded: boolean,
t: TFnType
t: TFunction
): ColumnDef<TResponseTableData>[] => {
const QUESTIONS_ICON_MAP = getQuestionIconMap(t);
const addressFields = ["addressLine1", "addressLine2", "city", "state", "zip", "country"];
@@ -231,7 +231,7 @@ const getQuestionColumnsData = (
}
};
const getMetadataColumnsData = (t: TFnType): ColumnDef<TResponseTableData>[] => {
const getMetadataColumnsData = (t: TFunction): ColumnDef<TResponseTableData>[] => {
const metadataColumns: ColumnDef<TResponseTableData>[] = [];
METADATA_FIELDS.forEach((label) => {
@@ -262,7 +262,7 @@ export const generateResponseTableColumns = (
survey: TSurvey,
isExpanded: boolean,
isReadOnly: boolean,
t: TFnType,
t: TFunction,
showQuotasColumn: boolean
): ColumnDef<TResponseTableData>[] => {
const questionColumns = survey.questions.flatMap((question) =>

View File

@@ -1,4 +1,4 @@
import { TFnType } from "@tolgee/react";
import { TFunction } from "i18next";
import { capitalize } from "lodash";
import {
AirplayIcon,
@@ -10,7 +10,7 @@ import {
} from "lucide-react";
import { TResponseMeta } from "@formbricks/types/responses";
export const getAddressFieldLabel = (field: string, t: TFnType) => {
export const getAddressFieldLabel = (field: string, t: TFunction) => {
switch (field) {
case "addressLine1":
return t("environments.surveys.responses.address_line_1");
@@ -29,7 +29,7 @@ export const getAddressFieldLabel = (field: string, t: TFnType) => {
}
};
export const getContactInfoFieldLabel = (field: string, t: TFnType) => {
export const getContactInfoFieldLabel = (field: string, t: TFunction) => {
switch (field) {
case "firstName":
return t("environments.surveys.responses.first_name");
@@ -46,7 +46,7 @@ export const getContactInfoFieldLabel = (field: string, t: TFnType) => {
}
};
export const getMetadataFieldLabel = (label: string, t: TFnType) => {
export const getMetadataFieldLabel = (label: string, t: TFunction) => {
switch (label) {
case "action":
return t("common.action");

View File

@@ -9,6 +9,7 @@ import { getSurvey } from "@/lib/survey/service";
import { getTagsByEnvironmentId } from "@/lib/tag/service";
import { getUser } from "@/lib/user/service";
import { findMatchingLocale } from "@/lib/utils/locale";
import { getTranslate } from "@/lingodotdev/server";
import { getSegments } from "@/modules/ee/contacts/segments/lib/segments";
import { getIsContactsEnabled, getIsQuotasEnabled } from "@/modules/ee/license-check/lib/utils";
import { getQuotas } from "@/modules/ee/quotas/lib/quotas";
@@ -17,7 +18,6 @@ import { getOrganizationIdFromEnvironmentId } from "@/modules/survey/lib/organiz
import { getOrganizationBilling } from "@/modules/survey/lib/survey";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
const Page = async (props) => {
const params = await props.params;

View File

@@ -1,7 +1,7 @@
"use client";
import { useTranslate } from "@tolgee/react";
import Link from "next/link";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyQuestionSummaryAddress } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { timeSince } from "@/lib/time";
@@ -18,7 +18,7 @@ interface AddressSummaryProps {
}
export const AddressSummary = ({ questionSummary, environmentId, survey, locale }: AddressSummaryProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
<QuestionSummaryHeader questionSummary={questionSummary} survey={survey} />

View File

@@ -1,7 +1,7 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { InboxIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyQuestionSummaryCta } from "@formbricks/types/surveys/types";
import { ProgressBar } from "@/modules/ui/components/progress-bar";
import { convertFloatToNDecimal } from "../lib/utils";
@@ -13,7 +13,7 @@ interface CTASummaryProps {
}
export const CTASummary = ({ questionSummary, survey }: CTASummaryProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">

View File

@@ -1,6 +1,6 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyQuestionSummaryCal } from "@formbricks/types/surveys/types";
import { convertFloatToNDecimal } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils";
import { ProgressBar } from "@/modules/ui/components/progress-bar";
@@ -13,7 +13,7 @@ interface CalSummaryProps {
}
export const CalSummary = ({ questionSummary, survey }: CalSummaryProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">

View File

@@ -1,6 +1,6 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import {
TI18nString,
TSurvey,
@@ -25,7 +25,7 @@ interface ConsentSummaryProps {
}
export const ConsentSummary = ({ questionSummary, survey, setFilter }: ConsentSummaryProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const summaryItems = [
{
title: t("common.accepted"),

View File

@@ -1,7 +1,7 @@
"use client";
import { useTranslate } from "@tolgee/react";
import Link from "next/link";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyQuestionSummaryContactInfo } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { timeSince } from "@/lib/time";
@@ -23,7 +23,7 @@ export const ContactInfoSummary = ({
survey,
locale,
}: ContactInfoSummaryProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
<QuestionSummaryHeader questionSummary={questionSummary} survey={survey} />

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import Link from "next/link";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyQuestionSummaryDate } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { timeSince } from "@/lib/time";
@@ -25,7 +25,7 @@ export const DateQuestionSummary = ({
survey,
locale,
}: DateQuestionSummary) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [visibleResponses, setVisibleResponses] = useState(10);
const handleLoadMore = () => {

View File

@@ -1,9 +1,9 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { DownloadIcon, FileIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyQuestionSummaryFileUpload } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { timeSince } from "@/lib/time";
@@ -27,7 +27,7 @@ export const FileUploadSummary = ({
locale,
}: FileUploadSummaryProps) => {
const [visibleResponses, setVisibleResponses] = useState(10);
const { t } = useTranslate();
const { t } = useTranslation();
const handleLoadMore = () => {
// Increase the number of visible responses by 10, not exceeding the total number of responses
setVisibleResponses((prevVisibleResponses) =>

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { InboxIcon, Link, MessageSquareTextIcon } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurveyQuestionSummaryHiddenFields } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
@@ -19,7 +19,7 @@ interface HiddenFieldsSummaryProps {
export const HiddenFieldsSummary = ({ environment, questionSummary, locale }: HiddenFieldsSummaryProps) => {
const [visibleResponses, setVisibleResponses] = useState(10);
const { t } = useTranslate();
const { t } = useTranslation();
const handleLoadMore = () => {
// Increase the number of visible responses by 10, not exceeding the total number of responses
setVisibleResponses((prevVisibleResponses) =>

View File

@@ -1,6 +1,6 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import {
TI18nString,
TSurvey,
@@ -24,7 +24,7 @@ interface MatrixQuestionSummaryProps {
}
export const MatrixQuestionSummary = ({ questionSummary, survey, setFilter }: MatrixQuestionSummaryProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const getOpacityLevel = (percentage: number): string => {
const parsedPercentage = percentage;
const opacity = parsedPercentage * 0.75 + 15;

View File

@@ -1,9 +1,9 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { InboxIcon } from "lucide-react";
import Link from "next/link";
import { Fragment, useState } from "react";
import { useTranslation } from "react-i18next";
import {
TI18nString,
TSurvey,
@@ -42,7 +42,7 @@ export const MultipleChoiceSummary = ({
survey,
setFilter,
}: MultipleChoiceSummaryProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [visibleOtherResponses, setVisibleOtherResponses] = useState(10);
const otherValue = questionSummary.question.choices.find((choice) => choice.id === "other")?.label.default;
// sort by count and transform to array

View File

@@ -1,6 +1,6 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import {
TI18nString,
TSurvey,
@@ -25,7 +25,7 @@ interface NPSSummaryProps {
}
export const NPSSummary = ({ questionSummary, survey, setFilter }: NPSSummaryProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const applyFilter = (group: string) => {
const filters = {
promoters: {

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import Link from "next/link";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyQuestionSummaryOpenText } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { timeSince } from "@/lib/time";
@@ -21,7 +21,7 @@ interface OpenTextSummaryProps {
}
export const OpenTextSummary = ({ questionSummary, environmentId, survey, locale }: OpenTextSummaryProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const [visibleResponses, setVisibleResponses] = useState(10);
const handleLoadMore = () => {

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { InboxIcon } from "lucide-react";
import Image from "next/image";
import { useTranslation } from "react-i18next";
import {
TI18nString,
TSurvey,
@@ -30,7 +30,7 @@ interface PictureChoiceSummaryProps {
export const PictureChoiceSummary = ({ questionSummary, survey, setFilter }: PictureChoiceSummaryProps) => {
const results = questionSummary.choices;
const { t } = useTranslate();
const { t } = useTranslation();
return (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { InboxIcon } from "lucide-react";
import type { JSX } from "react";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyQuestionSummary } from "@formbricks/types/surveys/types";
import { getTextContent } from "@formbricks/types/surveys/validation";
import { recallToHeadline } from "@/lib/utils/recall";
@@ -23,7 +23,7 @@ export const QuestionSummaryHeader = ({
showResponses = true,
survey,
}: HeadProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const questionType = getQuestionTypes(t).find((type) => type.id === questionSummary.question.type);
return (

View File

@@ -1,4 +1,4 @@
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyQuestionSummaryRanking } from "@formbricks/types/surveys/types";
import { getChoiceIdByValue } from "@/lib/response/utils";
import { IdBadge } from "@/modules/ui/components/id-badge";
@@ -12,7 +12,7 @@ interface RankingSummaryProps {
export const RankingSummary = ({ questionSummary, survey }: RankingSummaryProps) => {
// sort by count and transform to array
const { t } = useTranslate();
const { t } = useTranslation();
const results = Object.values(questionSummary.choices).sort((a, b) => {
return a.avgRanking - b.avgRanking; // Sort by count
});

View File

@@ -1,8 +1,8 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { CircleSlash2, SmileIcon, StarIcon } from "lucide-react";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import {
TI18nString,
TSurvey,
@@ -28,7 +28,7 @@ interface RatingSummaryProps {
}
export const RatingSummary = ({ questionSummary, survey, setFilter }: RatingSummaryProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const getIconBasedOnScale = useMemo(() => {
const scale = questionSummary.question.scale;
if (scale === "number") return <CircleSlash2 className="h-4 w-4" />;

View File

@@ -1,9 +1,9 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Confetti } from "@/modules/ui/components/confetti";
@@ -14,7 +14,7 @@ interface SummaryMetadataProps {
}
export const SuccessMessage = ({ environment, survey }: SummaryMetadataProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const searchParams = useSearchParams();
const [confetti, setConfetti] = useState(false);

View File

@@ -1,7 +1,7 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { TimerIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyQuestionType, TSurveySummary } from "@formbricks/types/surveys/types";
import { recallToHeadline } from "@/lib/utils/recall";
import { formatTextWithSlashes } from "@/modules/survey/editor/lib/utils";
@@ -14,7 +14,7 @@ interface SummaryDropOffsProps {
}
export const SummaryDropOffs = ({ dropOff, survey }: SummaryDropOffsProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const getIcon = (questionType: TSurveyQuestionType) => {
const Icon = getQuestionIcon(questionType, t);
return <Icon className="mt-[3px] h-5 w-5 shrink-0 text-slate-600" />;

View File

@@ -1,7 +1,7 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TI18nString, TSurveyQuestionId, TSurveySummary } from "@formbricks/types/surveys/types";
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
@@ -43,7 +43,7 @@ interface SummaryListProps {
export const SummaryList = ({ summary, environment, responseCount, survey, locale }: SummaryListProps) => {
const { setSelectedFilter, selectedFilter } = useResponseFilter();
const { t } = useTranslate();
const { t } = useTranslation();
const setFilter = (
questionId: TSurveyQuestionId,
label: TI18nString,

View File

@@ -1,6 +1,6 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { useTranslation } from "react-i18next";
import { TSurveySummary } from "@formbricks/types/surveys/types";
import { InteractiveCard } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/interactive-card";
import { StatCard } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/stat-card";
@@ -48,7 +48,7 @@ export const SummaryMetadata = ({
quotasCompleted,
quotasCompletedPercentage,
} = surveySummary;
const { t } = useTranslate();
const { t } = useTranslation();
const dropoffCountValue = dropOffCount === 0 ? <span>-</span> : dropOffCount;
const handleTabChange = (val: "dropOffs" | "quotas") => {

View File

@@ -1,10 +1,10 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { BellRing, Eye, ListRestart, SquarePenIcon } from "lucide-react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey } from "@formbricks/types/surveys/types";
@@ -54,7 +54,7 @@ export const SurveyAnalysisCTA = ({
isFormbricksCloud,
isStorageConfigured,
}: SurveyAnalysisCTAProps) => {
const { t } = useTranslate();
const { t } = useTranslation();
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();

View File

@@ -1,7 +1,6 @@
"use client";
import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
import { useTranslate } from "@tolgee/react";
import {
Code2Icon,
LinkIcon,
@@ -13,6 +12,7 @@ import {
UserIcon,
} from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TUser } from "@formbricks/types/user";
@@ -69,7 +69,7 @@ export const ShareSurveyModal = ({
const [surveyUrl, setSurveyUrl] = useState<string>(getSurveyUrl(survey, publicDomain, "default"));
const [showView, setShowView] = useState<ModalView>(modalView);
const { email } = user;
const { t } = useTranslate();
const { t } = useTranslation();
const linkTabs: {
id: ShareViaType | ShareSettingsType;
type: LinkTabsType;

Some files were not shown because too many files have changed in this diff Show More