├── .changeset ├── README.md └── config.json ├── .github └── workflows │ ├── jobs-production.yml │ ├── release-cli.yml │ └── web-production.yaml ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── action.yml ├── app.json ├── apps └── web │ ├── .env-example │ ├── README.md │ ├── components.json │ ├── drizzle.config.ts │ ├── drizzle │ ├── 0000_busy_mandrill.sql │ ├── 0001_steep_scrambler.sql │ └── meta │ │ ├── 0000_snapshot.json │ │ ├── 0001_snapshot.json │ │ └── _journal.json │ ├── languine.json │ ├── languine.lock │ ├── migrations │ ├── 0000_right_captain_marvel.sql │ ├── 0001_shocking_grim_reaper.sql │ ├── 0002_awesome_victor_mancha.sql │ ├── 0003_polite_gamma_corps.sql │ ├── 0004_dear_omega_red.sql │ ├── 0005_slimy_proteus.sql │ ├── 0006_tidy_wilson_fisk.sql │ ├── 0007_condemned_landau.sql │ ├── 0008_analytics_function.sql │ ├── 0008_striped_virginia_dare.sql │ └── meta │ │ ├── 0000_snapshot.json │ │ ├── 0001_snapshot.json │ │ ├── 0002_snapshot.json │ │ ├── 0003_snapshot.json │ │ ├── 0004_snapshot.json │ │ ├── 0005_snapshot.json │ │ ├── 0006_snapshot.json │ │ ├── 0007_snapshot.json │ │ ├── 0008_snapshot.json │ │ └── _journal.json │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ ├── email │ │ ├── github.png │ │ ├── logo.png │ │ ├── separator.png │ │ └── x.png │ ├── llm.txt │ ├── noise.png │ ├── robots.txt │ └── updates │ │ ├── api-and-sdk.png │ │ └── expo.png │ ├── src │ ├── actions │ │ ├── sign-out.ts │ │ └── support.ts │ ├── app │ │ ├── [locale] │ │ │ ├── (dashboard) │ │ │ │ ├── (sidebar) │ │ │ │ │ ├── [organization] │ │ │ │ │ │ └── [project] │ │ │ │ │ │ │ ├── overrides │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ │ ├── settings │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ │ ├── support │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ │ └── tuning │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── layout.tsx │ │ │ │ ├── cli │ │ │ │ │ └── success │ │ │ │ │ │ └── page.tsx │ │ │ │ └── login │ │ │ │ │ └── page.tsx │ │ │ ├── (marketing) │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── policy │ │ │ │ │ └── page.tsx │ │ │ │ ├── pricing │ │ │ │ │ └── page.tsx │ │ │ │ ├── terms │ │ │ │ │ └── page.tsx │ │ │ │ └── updates │ │ │ │ │ └── page.tsx │ │ │ ├── docs │ │ │ │ ├── [...all] │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── not-found.tsx │ │ │ └── opengraph-image.png │ │ ├── api │ │ │ ├── auth │ │ │ │ ├── callback │ │ │ │ │ └── route.ts │ │ │ │ └── cli │ │ │ │ │ └── [token] │ │ │ │ │ ├── route.ts │ │ │ │ │ └── verify │ │ │ │ │ └── route.ts │ │ │ ├── checkout │ │ │ │ └── route.ts │ │ │ ├── default-org │ │ │ │ └── route.ts │ │ │ ├── invite │ │ │ │ └── [inviteId] │ │ │ │ │ └── route.ts │ │ │ ├── portal │ │ │ │ └── route.ts │ │ │ ├── translate │ │ │ │ ├── route.ts │ │ │ │ └── utils │ │ │ │ │ ├── cache.ts │ │ │ │ │ ├── db.ts │ │ │ │ │ ├── errors.ts │ │ │ │ │ ├── translation.ts │ │ │ │ │ └── validation.ts │ │ │ ├── trpc │ │ │ │ └── [trpc] │ │ │ │ │ └── route.ts │ │ │ └── webhook │ │ │ │ └── polar │ │ │ │ └── route.ts │ │ ├── favicon.ico │ │ └── globals.css │ ├── components │ │ ├── activity-card.tsx │ │ ├── activity.tsx │ │ ├── billing-plan.tsx │ │ ├── change-language.tsx │ │ ├── charts │ │ │ └── analytics.tsx │ │ ├── companies.tsx │ │ ├── copy-button.tsx │ │ ├── copy-input.tsx │ │ ├── copy-install.tsx │ │ ├── current-tier.tsx │ │ ├── danger-zone.tsx │ │ ├── dashboard │ │ │ ├── header.tsx │ │ │ └── mobile-menu.tsx │ │ ├── docs-sidebar.tsx │ │ ├── dotted-separator.tsx │ │ ├── faq.tsx │ │ ├── features.tsx │ │ ├── filter-locales.tsx │ │ ├── footer-logo.tsx │ │ ├── footer.tsx │ │ ├── get-started.tsx │ │ ├── github-sign-in.tsx │ │ ├── github-stars.tsx │ │ ├── google-sign-in.tsx │ │ ├── header.tsx │ │ ├── hero.tsx │ │ ├── icons.tsx │ │ ├── info.tsx │ │ ├── login.tsx │ │ ├── logo-square.tsx │ │ ├── logo.tsx │ │ ├── matrix.tsx │ │ ├── mobile-menu.tsx │ │ ├── modals │ │ │ ├── change-plan.tsx │ │ │ ├── create-project.tsx │ │ │ ├── create-team.tsx │ │ │ ├── index.tsx │ │ │ └── invite.tsx │ │ ├── onboarding-steps.tsx │ │ ├── onboarding │ │ │ ├── step-1.tsx │ │ │ └── step-2.tsx │ │ ├── package-manager-context.tsx │ │ ├── package-manager-tabs.tsx │ │ ├── period-selector.tsx │ │ ├── pipeline.tsx │ │ ├── plan-settings.tsx │ │ ├── pricing-slider.tsx │ │ ├── pricing.tsx │ │ ├── search-input.tsx │ │ ├── settings-card.tsx │ │ ├── settings │ │ │ ├── account.tsx │ │ │ ├── billing.tsx │ │ │ ├── index.tsx │ │ │ ├── project.tsx │ │ │ └── team.tsx │ │ ├── sheets │ │ │ ├── index.tsx │ │ │ └── overrides.tsx │ │ ├── sidebar.tsx │ │ ├── sign-in.tsx │ │ ├── stacked-code.tsx │ │ ├── support-form.tsx │ │ ├── team-management.tsx │ │ ├── team-selector.server.tsx │ │ ├── team-selector.tsx │ │ ├── terminal.tsx │ │ ├── tuning.tsx │ │ ├── ui │ │ │ ├── accordion.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── alert.tsx │ │ │ ├── avatar.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── chart.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── input-otp.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── loader.tsx │ │ │ ├── outlined-button.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── slider.tsx │ │ │ ├── sonner.tsx │ │ │ ├── spinner.tsx │ │ │ ├── submit-button.tsx │ │ │ ├── switch.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ └── tooltip.tsx │ │ └── user-menu.tsx │ ├── contexts │ │ ├── docs.tsx │ │ └── session.tsx │ ├── db │ │ ├── index.ts │ │ ├── primary.ts │ │ ├── queries │ │ │ ├── analytics.ts │ │ │ ├── organization.ts │ │ │ ├── permissions.ts │ │ │ ├── project.ts │ │ │ ├── translate.ts │ │ │ └── user.ts │ │ └── schema.ts │ ├── emails │ │ ├── components │ │ │ ├── footer.tsx │ │ │ ├── logo.tsx │ │ │ └── outline-button.tsx │ │ └── templates │ │ │ ├── invite.tsx │ │ │ ├── support.tsx │ │ │ └── welcome.tsx │ ├── hooks │ │ ├── use-create-project-modal.ts │ │ ├── use-create-team-modal.ts │ │ ├── use-filters.ts │ │ ├── use-invite-modal.ts │ │ ├── use-media-query.ts │ │ ├── use-mobile.ts │ │ ├── use-overrides-sheet.ts │ │ ├── use-period.ts │ │ ├── use-plan-modal.tsx │ │ └── use-search.ts │ ├── i18n │ │ ├── request.ts │ │ └── routing.ts │ ├── jobs │ │ ├── translate │ │ │ ├── start-translations.ts │ │ │ └── translate-locale.ts │ │ └── utils │ │ │ ├── chunk.ts │ │ │ ├── locale.ts │ │ │ ├── model.ts │ │ │ ├── prompt.ts │ │ │ ├── tokeniser.ts │ │ │ ├── transform.ts │ │ │ ├── translate.ts │ │ │ └── types.ts │ ├── lib │ │ ├── auth │ │ │ └── cli.ts │ │ ├── checkout.ts │ │ ├── format.ts │ │ ├── kv.ts │ │ ├── markdown.tsx │ │ ├── polar.ts │ │ ├── resend.ts │ │ ├── schemas │ │ │ └── support.ts │ │ ├── tiers.ts │ │ ├── url.ts │ │ ├── user-preferences.ts │ │ └── utils.ts │ ├── markdown │ │ ├── docs │ │ │ └── en │ │ │ │ ├── android.mdx │ │ │ │ ├── api.mdx │ │ │ │ ├── arb.mdx │ │ │ │ ├── authentication.mdx │ │ │ │ ├── biome.mdx │ │ │ │ ├── ci.mdx │ │ │ │ ├── cli.mdx │ │ │ │ ├── configuration.mdx │ │ │ │ ├── csv.mdx │ │ │ │ ├── expo.mdx │ │ │ │ ├── ftl.mdx │ │ │ │ ├── github-actions.mdx │ │ │ │ ├── html.mdx │ │ │ │ ├── introduction.mdx │ │ │ │ ├── ios.mdx │ │ │ │ ├── js.mdx │ │ │ │ ├── json.mdx │ │ │ │ ├── md.mdx │ │ │ │ ├── mdx.mdx │ │ │ │ ├── overrides.mdx │ │ │ │ ├── php.mdx │ │ │ │ ├── po.mdx │ │ │ │ ├── prettier.mdx │ │ │ │ ├── properties.mdx │ │ │ │ ├── quickstart.mdx │ │ │ │ ├── react-email.mdx │ │ │ │ ├── sdk.mdx │ │ │ │ ├── settings.mdx │ │ │ │ ├── ts.mdx │ │ │ │ ├── tuning.mdx │ │ │ │ ├── vercel.mdx │ │ │ │ ├── xcode-stringsdict.mdx │ │ │ │ ├── xcode-xcstrings.mdx │ │ │ │ ├── xliff.mdx │ │ │ │ ├── xml.mdx │ │ │ │ └── yaml.mdx │ │ ├── pages │ │ │ ├── policy.mdx │ │ │ └── terms.mdx │ │ └── updates │ │ │ ├── api-and-sdk.mdx │ │ │ └── expo.mdx │ ├── mdx-components.tsx │ ├── messages │ │ ├── de.json │ │ ├── en.json │ │ ├── es.json │ │ ├── fi.json │ │ ├── fr.json │ │ ├── it.json │ │ ├── ko.json │ │ ├── nl.json │ │ ├── no.json │ │ ├── pl.json │ │ ├── pt.json │ │ └── sv.json │ ├── middleware.ts │ └── trpc │ │ ├── client.tsx │ │ ├── init.ts │ │ ├── permissions │ │ ├── organization.ts │ │ └── project.ts │ │ ├── query-client.ts │ │ ├── routers │ │ ├── _app.ts │ │ ├── analytics.ts │ │ ├── jobs.ts │ │ ├── jobs.utils.ts │ │ ├── organization.ts │ │ ├── project.ts │ │ ├── schema.ts │ │ ├── translate.ts │ │ └── user.ts │ │ └── server.ts │ ├── tailwind.config.ts │ ├── trigger.config.ts │ ├── tsconfig.json │ └── vercel.json ├── biome.json ├── bun.lock ├── examples ├── android │ ├── languine.json │ └── locales │ │ ├── en.xml │ │ └── es.xml ├── bun.lockb ├── complex-keys │ ├── languine.json │ ├── languine.lock │ └── messages │ │ ├── de.json │ │ └── en.json ├── expo │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── app.json │ ├── app │ │ ├── (tabs) │ │ │ ├── _layout.tsx │ │ │ ├── explore.tsx │ │ │ └── index.tsx │ │ ├── +not-found.tsx │ │ └── _layout.tsx │ ├── assets │ │ ├── fonts │ │ │ └── SpaceMono-Regular.ttf │ │ └── images │ │ │ ├── adaptive-icon.png │ │ │ ├── favicon.png │ │ │ ├── icon.png │ │ │ ├── partial-react-logo.png │ │ │ ├── react-logo.png │ │ │ ├── react-logo@2x.png │ │ │ ├── react-logo@3x.png │ │ │ └── splash-icon.png │ ├── components │ │ ├── Collapsible.tsx │ │ ├── ExternalLink.tsx │ │ ├── HapticTab.tsx │ │ ├── HelloWave.tsx │ │ ├── ParallaxScrollView.tsx │ │ ├── ThemedText.tsx │ │ ├── ThemedView.tsx │ │ ├── __tests__ │ │ │ ├── ThemedText-test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── ThemedText-test.tsx.snap │ │ └── ui │ │ │ ├── IconSymbol.ios.tsx │ │ │ ├── IconSymbol.tsx │ │ │ ├── TabBarBackground.ios.tsx │ │ │ └── TabBarBackground.tsx │ ├── constants │ │ └── Colors.ts │ ├── hooks │ │ ├── useColorScheme.ts │ │ ├── useColorScheme.web.ts │ │ └── useThemeColor.ts │ ├── languine.json │ ├── languine.lock │ ├── locales │ │ ├── README.md │ │ ├── en.json │ │ ├── es.json │ │ ├── i18n.ts │ │ ├── native │ │ │ ├── en.json │ │ │ ├── es.json │ │ │ └── sv.json │ │ └── sv.json │ ├── package-lock.json │ ├── package.json │ ├── scripts │ │ └── reset-project.js │ └── tsconfig.json ├── ftl │ ├── languine.json │ ├── languine.lock │ └── locales │ │ ├── en.ftl │ │ └── sv.ftl ├── fumadocs │ ├── .gitignore │ ├── app │ │ ├── [lang] │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── api │ │ │ └── search │ │ │ │ └── route.ts │ │ └── layout.config.tsx │ ├── content │ │ ├── docs │ │ │ ├── cn │ │ │ │ ├── index.mdx │ │ │ │ └── test.mdx │ │ │ └── en │ │ │ │ ├── index.mdx │ │ │ │ └── test.mdx │ │ ├── ui.cn.json │ │ ├── ui.en.json │ │ └── ui.json │ ├── languine.json │ ├── lib │ │ ├── i18n.ts │ │ └── source.ts │ ├── middleware.ts │ ├── next.config.mjs │ ├── package.json │ ├── source.config.ts │ └── tsconfig.json ├── i18next │ ├── languine.json │ ├── languine.lock │ └── locales │ │ ├── en.json │ │ ├── es.json │ │ └── fr.json ├── lingui │ ├── languine.json │ └── locales │ │ ├── de │ │ └── messages.po │ │ └── en │ │ └── messages.po ├── markdown │ ├── blog │ │ ├── en │ │ │ ├── assistant.mdx │ │ │ └── dub.md │ │ └── sv │ │ │ ├── assistant.mdx │ │ │ └── dub.md │ ├── languine.json │ └── languine.lock ├── monorepo │ └── apps │ │ └── web │ │ └── src │ │ └── locales │ │ └── en.json ├── multiple │ ├── languine.json │ ├── languine.lock │ └── src │ │ └── locales │ │ ├── en │ │ ├── hero.json │ │ └── menu.json │ │ └── sv │ │ ├── hero.json │ │ └── menu.json ├── namespace │ ├── docs │ │ ├── de │ │ │ └── welcome.md │ │ ├── en │ │ │ └── welcome.md │ │ ├── es │ │ │ └── welcome.md │ │ ├── fr │ │ │ └── welcome.md │ │ └── sv │ │ │ └── welcome.md │ ├── languine.json │ └── locales │ │ ├── en │ │ ├── common.json │ │ ├── footer.json │ │ └── header.json │ │ ├── es │ │ ├── common.json │ │ ├── footer.json │ │ └── header.json │ │ └── fr │ │ ├── common.json │ │ ├── footer.json │ │ └── header.json ├── next-international │ ├── languine.json │ ├── languine.lock │ └── locales │ │ ├── de.ts │ │ ├── en.ts │ │ ├── es.ts │ │ ├── fi.ts │ │ ├── fr.ts │ │ ├── it.ts │ │ ├── nl.ts │ │ ├── no.ts │ │ ├── pl.ts │ │ ├── pt.ts │ │ └── sv.ts ├── next-intl │ ├── languine.json │ ├── languine.lock │ └── messages │ │ ├── de.json │ │ └── en.json ├── nuxt │ ├── i18n │ │ └── locales │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ ├── es.json │ │ │ ├── fr.json │ │ │ └── sv.json │ ├── languine.json │ └── src │ │ └── i18n │ │ └── locales │ │ ├── de.json │ │ ├── en.json │ │ ├── es.json │ │ ├── fr.json │ │ └── sv.json ├── overrides │ ├── languine.json │ ├── languine.lock │ └── src │ │ └── locales │ │ ├── en.json │ │ └── ru.json ├── package.json ├── php │ ├── lang │ │ ├── en │ │ │ └── common.php │ │ ├── es │ │ │ └── common.php │ │ └── sv │ │ │ └── common.php │ ├── languine.json │ └── languine.lock ├── po │ ├── languine.json │ └── locales │ │ ├── en.po │ │ └── es.po ├── react-email │ ├── emails │ │ ├── static │ │ │ ├── vercel-arrow.png │ │ │ ├── vercel-logo.png │ │ │ ├── vercel-team.png │ │ │ └── vercel-user.png │ │ └── vercel-invite-user.tsx │ ├── languine.json │ ├── locales │ │ ├── en.json │ │ ├── es.json │ │ ├── pt.json │ │ └── sv.json │ ├── package.json │ └── readme.md ├── react-i18next │ ├── languine.json │ ├── languine.lock │ └── locales │ │ ├── en.json │ │ ├── es.json │ │ └── sv.json ├── transform │ ├── languine.json │ └── src │ │ └── components │ │ ├── example.tsx │ │ ├── header.tsx │ │ ├── hero.tsx │ │ └── react-native.tsx ├── xcode-strings │ ├── Example │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ └── es.lproj │ │ │ └── Localizable.strings │ └── languine.json ├── xcode-stringsdict │ ├── Example │ │ ├── de.lproj │ │ │ └── Localizable.stringsdict │ │ ├── en.lproj │ │ │ └── Localizable.stringsdict │ │ └── es.lproj │ │ │ └── Localizable.stringsdict │ └── languine.json ├── xcode-xcstrings │ ├── Example │ │ └── Localizable.xcstrings │ └── languine.json └── yaml │ ├── languine.json │ └── locales │ ├── en.yml │ ├── es.yml │ └── fr.yml ├── package.json ├── packages ├── action │ ├── Dockerfile │ ├── bun.lock │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── platforms │ │ │ ├── git-provider-factory.ts │ │ │ ├── github.ts │ │ │ └── provider.ts │ │ ├── services │ │ │ └── translation.ts │ │ ├── tsconfig.json │ │ ├── types.ts │ │ ├── utils │ │ │ ├── config.ts │ │ │ ├── exec.ts │ │ │ └── logger.ts │ │ └── workflows │ │ │ ├── branch.ts │ │ │ ├── factory.ts │ │ │ └── pull-request.ts │ └── tsconfig.json ├── cli │ ├── .env-template │ ├── CHANGELOG.md │ ├── README.md │ ├── languine.lock │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── config.test.ts │ │ │ └── locale.test.ts │ │ ├── commands │ │ │ ├── auth │ │ │ │ ├── index.ts │ │ │ │ ├── login.ts │ │ │ │ ├── logout.ts │ │ │ │ └── whoami.ts │ │ │ ├── init.ts │ │ │ ├── locale.ts │ │ │ ├── overrides │ │ │ │ ├── index.ts │ │ │ │ └── pull.ts │ │ │ ├── run.ts │ │ │ ├── sync.ts │ │ │ ├── transform.ts │ │ │ ├── translate.ts │ │ │ └── translations │ │ │ │ ├── delete.ts │ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── parsers │ │ │ ├── __tests__ │ │ │ │ ├── android.test.ts │ │ │ │ ├── arb.test.ts │ │ │ │ ├── csv.test.ts │ │ │ │ ├── ftl.test.ts │ │ │ │ ├── html.test.ts │ │ │ │ ├── javascript.test.ts │ │ │ │ ├── json.test.ts │ │ │ │ ├── php.test.ts │ │ │ │ ├── po.test.ts │ │ │ │ ├── properties.test.ts │ │ │ │ ├── xcode-strings.test.ts │ │ │ │ ├── xcode-stringsdict.test.ts │ │ │ │ ├── xcode-xcstrings.test.ts │ │ │ │ ├── xliff.test.ts │ │ │ │ ├── xml.test.ts │ │ │ │ └── yaml.test.ts │ │ │ ├── core │ │ │ │ ├── base-parser.ts │ │ │ │ ├── flatten.test.ts │ │ │ │ ├── flatten.ts │ │ │ │ ├── format.ts │ │ │ │ └── types.ts │ │ │ ├── formats │ │ │ │ ├── android.ts │ │ │ │ ├── arb.ts │ │ │ │ ├── csv.ts │ │ │ │ ├── ftl.ts │ │ │ │ ├── html.ts │ │ │ │ ├── javascript.ts │ │ │ │ ├── json.ts │ │ │ │ ├── markdown.ts │ │ │ │ ├── php.ts │ │ │ │ ├── po.ts │ │ │ │ ├── properties.ts │ │ │ │ ├── types.ts │ │ │ │ ├── xcode-strings.ts │ │ │ │ ├── xcode-stringsdict.ts │ │ │ │ ├── xcode-xcstrings.ts │ │ │ │ ├── xliff.ts │ │ │ │ ├── xml.ts │ │ │ │ └── yaml.ts │ │ │ └── index.ts │ │ ├── presets │ │ │ └── expo.ts │ │ ├── types.ts │ │ ├── types │ │ │ └── jscodeshift.d.ts │ │ └── utils │ │ │ ├── __tests__ │ │ │ └── path.test.ts │ │ │ ├── api.ts │ │ │ ├── config.ts │ │ │ ├── env.ts │ │ │ ├── exec.ts │ │ │ ├── git.ts │ │ │ ├── lock.ts │ │ │ ├── path.ts │ │ │ ├── session.ts │ │ │ └── transform.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── types │ │ └── xliff.d.ts ├── react-email │ ├── CHANGELOG.md │ ├── README.md │ ├── image.png │ ├── package.json │ ├── src │ │ ├── index.tsx │ │ ├── interpolate.tsx │ │ └── loader.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── sdk │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── types.ts │ ├── tsconfig.json │ └── tsup.config.ts └── supabase │ ├── package.json │ ├── src │ ├── auth │ │ ├── client.ts │ │ └── session.ts │ └── client │ │ ├── client.ts │ │ ├── middleware.ts │ │ ├── server.ts │ │ └── utils.ts │ └── tsconfig.json └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "midday-ai/languine" }], 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "ignore": [], 10 | "updateInternalDependencies": "patch", 11 | "bumpStrategy": "bump" 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/jobs-production.yml: -------------------------------------------------------------------------------- 1 | name: Jobs Production 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - apps/web/src/jobs/** 8 | - packages/supabase/** 9 | jobs: 10 | deploy-production: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: oven-sh/setup-bun@v1 15 | with: 16 | bun-version: latest 17 | - name: Install dependencies 18 | run: bun install 19 | - name: 🔄 Deploy Background Jobs 20 | env: 21 | TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} 22 | run: | 23 | TRIGGER_PROJECT_ID=${{ secrets.TRIGGER_PROJECT_ID }} bunx trigger.dev@3.3.17 deploy 24 | working-directory: apps/web -------------------------------------------------------------------------------- /.github/workflows/web-production.yaml: -------------------------------------------------------------------------------- 1 | name: Web Deploy - Production 2 | on: 3 | push: 4 | branches: 5 | - '**' 6 | paths: 7 | - apps/web/** 8 | jobs: 9 | deploy-production: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - name: 🔄 Run Languine CLI 19 | uses: ./ 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | with: 23 | api-key: ${{ secrets.LANGUINE_API_KEY }} 24 | project-id: ${{ secrets.LANGUINE_PROJECT_ID }} 25 | working-directory: apps/web 26 | # cli-version: canary 27 | # create-pull-request: true 28 | dev-mode: true 29 | 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | package-lock.json 8 | 9 | # testing 10 | coverage 11 | 12 | # next.js 13 | .next/ 14 | out/ 15 | next-env.d.ts 16 | 17 | # production 18 | build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | .pnpm-debug.log* 29 | 30 | # local env files 31 | .env 32 | .env*.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | 40 | # turbo 41 | .turbo 42 | 43 | dist 44 | 45 | .react-email 46 | 47 | 48 | # prod 49 | dist/ 50 | 51 | # dev 52 | .yarn/ 53 | !.yarn/releases 54 | .vscode/* 55 | !.vscode/launch.json 56 | !.vscode/*.code-snippets 57 | .idea/workspace.xml 58 | .idea/usage.statistics.xml 59 | .idea/shelf 60 | 61 | # deps 62 | node_modules/ 63 | 64 | # env 65 | .env 66 | .env.production 67 | .dev.vars 68 | 69 | # logs 70 | logs/ 71 | *.log 72 | npm-debug.log* 73 | yarn-debug.log* 74 | yarn-error.log* 75 | pnpm-debug.log* 76 | lerna-debug.log* 77 | 78 | # misc 79 | .DS_Store 80 | 81 | .trigger 82 | .test-output -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "biomejs.biome", 3 | "[javascript]": { 4 | "editor.defaultFormatter": "biomejs.biome" 5 | }, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "biomejs.biome" 8 | }, 9 | "[typescriptreact]": { 10 | "editor.defaultFormatter": "biomejs.biome" 11 | }, 12 | "editor.codeActionsOnSave": { 13 | "quickfix.biome": "explicit", 14 | "source.organizeImports.biome": "explicit" 15 | }, 16 | "editor.formatOnSave": true, 17 | "typescript.enablePromptUseWorkspaceTsdk": true, 18 | "typescript.tsdk": "node_modules/typescript/lib", 19 | "typescript.preferences.autoImportFileExcludePatterns": [ 20 | "next/router.d.ts", 21 | "next/dist/client/router.d.ts" 22 | ], 23 | "terminal.integrated.localEchoStyle": "dim", 24 | "search.exclude": { 25 | "**/node_modules": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "expo-localization" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/.env-example: -------------------------------------------------------------------------------- 1 | RESEND_API_KEY= 2 | UPSTASH_REDIS_REST_URL= 3 | UPSTASH_REDIS_REST_TOKEN= 4 | TRIGGER_SECRET_KEY= 5 | NEXT_PUBLIC_APP_URL= 6 | 7 | # Primary Model 8 | AI_PRIMARY_ENDPOINT= 9 | AI_PRIMARY_MODEL= 10 | AI_PRIMARY_API_KEY= 11 | 12 | # Secondary Model 13 | AI_SECONDARY_ENDPOINT= 14 | AI_SECONDARY_MODEL= 15 | AI_SECONDARY_API_KEY= 16 | 17 | # Supabase 18 | NEXT_PUBLIC_SUPABASE_URL= 19 | NEXT_PUBLIC_SUPABASE_ANON_KEY= 20 | 21 | # Database 22 | DATABASE_PRIMARY_URL= 23 | DATABASE_US_URL= 24 | DATABASE_EU_URL= 25 | DATABASE_AU_URL= 26 | 27 | # Polar 28 | POLAR_ACCESS_TOKEN= 29 | POLAR_ORGANIZATION_ID= 30 | POLAR_ENVIRONMENT= 31 | POLAR_WEBHOOK_SECRET= -------------------------------------------------------------------------------- /apps/web/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/README.md -------------------------------------------------------------------------------- /apps/web/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | import type { Config } from "drizzle-kit"; 4 | 5 | export default { 6 | schema: "./src/db/schema.ts", 7 | out: "./migrations", 8 | dialect: "postgresql", 9 | dbCredentials: { 10 | url: process.env.DATABASE_PRIMARY_URL!, 11 | }, 12 | } satisfies Config; 13 | -------------------------------------------------------------------------------- /apps/web/drizzle/0001_steep_scrambler.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `projects` ADD `slug` text NOT NULL;--> statement-breakpoint 2 | CREATE UNIQUE INDEX `slug_org_idx` ON `projects` (`slug`,`organization_id`); -------------------------------------------------------------------------------- /apps/web/drizzle/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7", 3 | "dialect": "sqlite", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "6", 8 | "when": 1736171596544, 9 | "tag": "0000_busy_mandrill", 10 | "breakpoints": true 11 | }, 12 | { 13 | "idx": 1, 14 | "version": "6", 15 | "when": 1736177427739, 16 | "tag": "0001_steep_scrambler", 17 | "breakpoints": true 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /apps/web/languine.json: -------------------------------------------------------------------------------- 1 | { 2 | "locale": { 3 | "source": "en", 4 | "targets": ["de", "es", "fi", "fr", "it", "ko", "nl", "no", "pl", "pt", "sv"] 5 | }, 6 | "files": { 7 | "json": { 8 | "include": ["src/messages/[locale].json"] 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /apps/web/migrations/0001_shocking_grim_reaper.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "organizations" ALTER COLUMN "tier" SET DATA TYPE integer;--> statement-breakpoint 2 | ALTER TABLE "organizations" ALTER COLUMN "tier" SET DEFAULT 0; -------------------------------------------------------------------------------- /apps/web/migrations/0002_awesome_victor_mancha.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "users" DROP COLUMN "email_verified"; -------------------------------------------------------------------------------- /apps/web/migrations/0003_polite_gamma_corps.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "accounts" CASCADE;--> statement-breakpoint 2 | DROP TABLE "verifications" CASCADE; -------------------------------------------------------------------------------- /apps/web/migrations/0006_tidy_wilson_fisk.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "members" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint 2 | ALTER TABLE "organizations" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint 3 | ALTER TABLE "project_settings" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint 4 | ALTER TABLE "projects" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint 5 | ALTER TABLE "projects" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint 6 | ALTER TABLE "projects" ALTER COLUMN "updated_at" SET NOT NULL;--> statement-breakpoint 7 | ALTER TABLE "translations" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint 8 | ALTER TABLE "translations" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint 9 | ALTER TABLE "users" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint 10 | ALTER TABLE "users" ALTER COLUMN "updated_at" SET DEFAULT now(); -------------------------------------------------------------------------------- /apps/web/migrations/0007_condemned_landau.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "organizations" DROP CONSTRAINT "organizations_slug_unique";--> statement-breakpoint 2 | DROP INDEX "slug_idx";--> statement-breakpoint 3 | ALTER TABLE "organizations" DROP COLUMN "slug"; -------------------------------------------------------------------------------- /apps/web/migrations/0008_striped_virginia_dare.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "translations" ADD COLUMN "overridden" boolean DEFAULT false NOT NULL; -------------------------------------------------------------------------------- /apps/web/next.config.ts: -------------------------------------------------------------------------------- 1 | import createMDX from "@next/mdx"; 2 | import type { NextConfig } from "next"; 3 | import createNextIntlPlugin from "next-intl/plugin"; 4 | 5 | const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts"); 6 | 7 | const nextConfig: NextConfig = { 8 | experimental: { 9 | inlineCss: true, 10 | }, 11 | typescript: { 12 | ignoreBuildErrors: true, 13 | }, 14 | eslint: { 15 | ignoreDuringBuilds: true, 16 | }, 17 | images: { 18 | remotePatterns: [ 19 | { 20 | hostname: "**", 21 | }, 22 | ], 23 | }, 24 | redirects: async () => { 25 | return [ 26 | { 27 | source: "/:locale/docs", 28 | destination: "/:locale/docs/introduction", 29 | permanent: true, 30 | }, 31 | ]; 32 | }, 33 | }; 34 | 35 | const withMDX = createMDX(); 36 | 37 | export default withNextIntl(withMDX(nextConfig)); 38 | -------------------------------------------------------------------------------- /apps/web/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /apps/web/public/email/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/public/email/github.png -------------------------------------------------------------------------------- /apps/web/public/email/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/public/email/logo.png -------------------------------------------------------------------------------- /apps/web/public/email/separator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/public/email/separator.png -------------------------------------------------------------------------------- /apps/web/public/email/x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/public/email/x.png -------------------------------------------------------------------------------- /apps/web/public/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/public/noise.png -------------------------------------------------------------------------------- /apps/web/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | 4 | Host: https://languine.ai 5 | Sitemap: https://languine.ai/sitemap.xml 6 | -------------------------------------------------------------------------------- /apps/web/public/updates/api-and-sdk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/public/updates/api-and-sdk.png -------------------------------------------------------------------------------- /apps/web/public/updates/expo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/public/updates/expo.png -------------------------------------------------------------------------------- /apps/web/src/actions/sign-out.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { createClient } from "@languine/supabase/server"; 4 | import { cookies } from "next/headers"; 5 | import { redirect } from "next/navigation"; 6 | 7 | export async function signOut() { 8 | const cookieStore = await cookies(); 9 | const supabase = await createClient(); 10 | 11 | // Sign out from Supabase 12 | await supabase.auth.signOut(); 13 | 14 | // Remove skip-session-refresh cookie 15 | cookieStore.delete("skip-session-refresh"); 16 | 17 | // Redirect to login page 18 | redirect("/login"); 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(dashboard)/(sidebar)/[organization]/[project]/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import { Settings } from "@/components/settings"; 2 | import { HydrateClient, trpc } from "@/trpc/server"; 3 | 4 | export default async function Page({ 5 | params, 6 | }: { 7 | params: Promise<{ organization: string; project: string }>; 8 | }) { 9 | const { organization, project } = await params; 10 | 11 | trpc.project.getBySlug.prefetch({ 12 | slug: project, 13 | organizationId: organization, 14 | }); 15 | 16 | trpc.user.me.prefetch(); 17 | 18 | trpc.organization.getById.prefetch({ 19 | organizationId: organization, 20 | }); 21 | 22 | trpc.organization.getStats.prefetch({ 23 | organizationId: organization, 24 | }); 25 | 26 | return ( 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(dashboard)/(sidebar)/[organization]/[project]/support/page.tsx: -------------------------------------------------------------------------------- 1 | import { SupportForm } from "@/components/support-form"; 2 | import { getTranslations } from "next-intl/server"; 3 | 4 | export default async function Page() { 5 | const t = await getTranslations("support"); 6 | 7 | return ( 8 |
9 |

10 | {t("title")} 11 |

12 | 13 |
14 | 15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(dashboard)/(sidebar)/[organization]/[project]/tuning/page.tsx: -------------------------------------------------------------------------------- 1 | import { Tuning } from "@/components/tuning"; 2 | import { HydrateClient, trpc } from "@/trpc/server"; 3 | 4 | export default async function Page({ 5 | params, 6 | }: { 7 | params: Promise<{ organization: string; project: string }>; 8 | }) { 9 | const { organization, project } = await params; 10 | 11 | trpc.project.getBySlug.prefetch({ 12 | slug: project, 13 | organizationId: organization, 14 | }); 15 | 16 | return ( 17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(dashboard)/cli/success/page.tsx: -------------------------------------------------------------------------------- 1 | import { getSession } from "@languine/supabase/session"; 2 | import { getTranslations } from "next-intl/server"; 3 | import { redirect } from "next/navigation"; 4 | 5 | export default async function Page() { 6 | const t = await getTranslations(); 7 | const { 8 | data: { session }, 9 | } = await getSession(); 10 | 11 | if (!session) { 12 | redirect("/login"); 13 | } 14 | 15 | return ( 16 |
17 |

{t("cli.success.title")}

18 |

19 | {t("cli.success.description")} {session.user.email} 20 |

21 |

{t("cli.success.description_2")}

22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(marketing)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Footer } from "@/components/footer"; 2 | import { Header } from "@/components/header"; 3 | import { routing } from "@/i18n/routing"; 4 | import { setRequestLocale } from "next-intl/server"; 5 | import type { ReactElement } from "react"; 6 | 7 | export function generateStaticParams() { 8 | return routing.locales.map((locale) => ({ locale })); 9 | } 10 | 11 | export default async function Layout({ 12 | children, 13 | params, 14 | }: { 15 | children: ReactElement; 16 | params: Promise<{ locale: string }>; 17 | }) { 18 | const { locale } = await params; 19 | 20 | setRequestLocale(locale); 21 | 22 | return ( 23 |
24 |
25 | 26 |
{children}
27 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(marketing)/page.tsx: -------------------------------------------------------------------------------- 1 | import { Companies } from "@/components/companies"; 2 | import { DottedSeparator } from "@/components/dotted-separator"; 3 | import { Features } from "@/components/features"; 4 | import { Hero } from "@/components/hero"; 5 | import { Info } from "@/components/info"; 6 | import { Pipeline } from "@/components/pipeline"; 7 | import { getTranslations } from "next-intl/server"; 8 | 9 | export async function generateMetadata() { 10 | const t = await getTranslations(); 11 | 12 | return { 13 | title: `Languine - ${t("hero.title")}`, 14 | description: t("hero.description"), 15 | }; 16 | } 17 | 18 | export default function Page() { 19 | return ( 20 |
21 | 22 | 23 |
24 | {/* */} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(marketing)/policy/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Policy from "@/markdown/pages/policy.mdx"; 4 | 5 | export default function Page() { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(marketing)/pricing/page.tsx: -------------------------------------------------------------------------------- 1 | import { DottedSeparator } from "@/components/dotted-separator"; 2 | import { FAQ } from "@/components/faq"; 3 | import { Info } from "@/components/info"; 4 | import { Pricing } from "@/components/pricing"; 5 | 6 | export default function Page() { 7 | return ( 8 |
9 | 10 | 11 | 12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(marketing)/terms/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Terms from "@/markdown/pages/terms.mdx"; 4 | 5 | export default function Page() { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/(marketing)/updates/page.tsx: -------------------------------------------------------------------------------- 1 | import { PackageManagerProvider } from "@/components/package-manager-context"; 2 | import { getUpdates } from "@/lib/markdown"; 3 | import type { Metadata } from "next"; 4 | 5 | export const metadata: Metadata = { 6 | title: "Updates", 7 | description: "Latest updates and announcements from Languine", 8 | }; 9 | 10 | interface Update { 11 | slug: string; 12 | frontmatter: { 13 | title: string; 14 | description: string; 15 | date: string; 16 | }; 17 | } 18 | 19 | export default async function Page() { 20 | const updates = await getUpdates(); 21 | 22 | return ( 23 | 24 |
25 | {updates} 26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/docs/[...all]/page.tsx: -------------------------------------------------------------------------------- 1 | import { getMarkdownContent } from "@/lib/markdown"; 2 | import { notFound } from "next/navigation"; 3 | 4 | export default async function Page({ 5 | params, 6 | }: { params: Promise<{ all: string[] }> }) { 7 | const { all } = await params; 8 | 9 | try { 10 | const content = await getMarkdownContent("en", all?.at(0) ?? ""); 11 | return content; 12 | } catch (error) { 13 | notFound(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { DocsNavigation, DocsSidebar } from "@/components/docs-sidebar"; 2 | import { Header } from "@/components/header"; 3 | import { DocsProvider } from "@/contexts/docs"; 4 | 5 | export default function DocsLayout({ 6 | children, 7 | }: { children: React.ReactNode }) { 8 | return ( 9 | 10 |
11 |
12 | 13 |
14 | 15 | 16 |
17 | {children} 18 | 19 |
20 |
21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/docs/page.tsx: -------------------------------------------------------------------------------- 1 | import { getMarkdownContent } from "@/lib/markdown"; 2 | import { notFound } from "next/navigation"; 3 | 4 | export default async function Page({ 5 | params, 6 | }: { params: Promise<{ locale: string }> }) { 7 | const { locale } = await params; 8 | 9 | try { 10 | const content = await getMarkdownContent(locale, "introduction"); 11 | return content; 12 | } catch (error) { 13 | notFound(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export default function NotFound() { 4 | return ( 5 |
6 |

Not Found

7 |

Could not find requested resource

8 | 9 | Return Home 10 | 11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/src/app/[locale]/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/src/app/[locale]/opengraph-image.png -------------------------------------------------------------------------------- /apps/web/src/app/api/auth/cli/[token]/route.ts: -------------------------------------------------------------------------------- 1 | import { CLI_TOKEN_NAME, saveCLISession } from "@/lib/auth/cli"; 2 | import { getSession } from "@languine/supabase/session"; 3 | import { NextResponse } from "next/server"; 4 | 5 | export async function GET( 6 | request: Request, 7 | { params }: { params: Promise<{ token: string }> }, 8 | ) { 9 | const { token } = await params; 10 | 11 | const { 12 | data: { session }, 13 | } = await getSession(); 14 | 15 | if (!session) { 16 | const response = NextResponse.redirect(new URL("/login", request.url), { 17 | status: 302, 18 | }); 19 | 20 | if (token) { 21 | response.cookies.set(CLI_TOKEN_NAME, token, { 22 | maxAge: 5 * 60, 23 | }); 24 | } 25 | 26 | return response; 27 | } 28 | 29 | if (session) { 30 | await saveCLISession(session, token); 31 | } 32 | 33 | const response = NextResponse.redirect(new URL("/cli/success", request.url), { 34 | status: 302, 35 | }); 36 | 37 | response.cookies.delete(CLI_TOKEN_NAME); 38 | 39 | return response; 40 | } 41 | -------------------------------------------------------------------------------- /apps/web/src/app/api/auth/cli/[token]/verify/route.ts: -------------------------------------------------------------------------------- 1 | import { connectDb } from "@/db"; 2 | import { users } from "@/db/schema"; 3 | import { getCLISession } from "@/lib/auth/cli"; 4 | import { eq } from "drizzle-orm"; 5 | import { NextResponse } from "next/server"; 6 | 7 | export async function GET( 8 | request: Request, 9 | { params }: { params: Promise<{ token: string }> }, 10 | ) { 11 | const { token } = await params; 12 | 13 | const cliSession = await getCLISession(token); 14 | 15 | if (!cliSession) { 16 | return NextResponse.json( 17 | { 18 | success: false, 19 | }, 20 | { status: 404 }, 21 | ); 22 | } 23 | 24 | const db = await connectDb(); 25 | 26 | const user = await db.query.users.findFirst({ 27 | where: eq(users.id, cliSession.user.id), 28 | columns: { 29 | id: true, 30 | name: true, 31 | email: true, 32 | apiKey: true, 33 | }, 34 | }); 35 | 36 | return NextResponse.json({ 37 | success: true, 38 | user, 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /apps/web/src/app/api/default-org/route.ts: -------------------------------------------------------------------------------- 1 | import { getOrganizationByUserId } from "@/db/queries/organization"; 2 | import { getSession } from "@languine/supabase/session"; 3 | import { cookies } from "next/headers"; 4 | import { redirect } from "next/navigation"; 5 | 6 | export async function GET() { 7 | const cookieStore = await cookies(); 8 | const { 9 | data: { session }, 10 | } = await getSession(); 11 | 12 | // Delete user preferences cookie 13 | cookieStore.delete("user-preferences"); 14 | 15 | if (session?.user.id) { 16 | const organization = await getOrganizationByUserId(session.user.id); 17 | 18 | // If user is part of an organization, redirect to the organization's default project 19 | if (organization) { 20 | redirect(`/${organization.organization.id}/default`); 21 | } 22 | } 23 | 24 | redirect("/login"); 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/src/app/api/portal/route.ts: -------------------------------------------------------------------------------- 1 | import { api } from "@/lib/polar"; 2 | import { getSession } from "@languine/supabase/session"; 3 | import { type NextRequest, NextResponse } from "next/server"; 4 | 5 | export async function GET(req: NextRequest) { 6 | const { 7 | data: { session }, 8 | } = await getSession(); 9 | 10 | if (!session?.user?.id) { 11 | throw new Error("You must be logged in"); 12 | } 13 | 14 | const customerId = req.nextUrl.searchParams.get("id"); 15 | 16 | if (!customerId) { 17 | throw new Error("Customer ID is required"); 18 | } 19 | 20 | const result = await api.customerSessions.create({ 21 | customerId, 22 | }); 23 | 24 | return NextResponse.redirect(result.customerPortalUrl); 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/src/app/api/translate/utils/cache.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "node:crypto"; 2 | import { kv } from "@/lib/kv"; 3 | 4 | // Cache TTL in seconds (8 hours) 5 | export const CACHE_TTL = 60 * 60 * 8; 6 | 7 | export const generateKey = (content: string): string => { 8 | return `api_${createHash("sha256").update(content).digest("hex").slice(0, 8)}`; 9 | }; 10 | 11 | export const getCacheKey = ( 12 | projectId: string, 13 | sourceLocale: string, 14 | targetLocale: string, 15 | key: string, 16 | ): string => { 17 | return `translate:${projectId}:${sourceLocale}:${targetLocale}:${key}`; 18 | }; 19 | 20 | export const getFromCache = async ( 21 | cacheKey: string, 22 | ): Promise => { 23 | return kv.get(cacheKey); 24 | }; 25 | 26 | export const setInCache = async ( 27 | cacheKey: string, 28 | value: string, 29 | ttl: number = CACHE_TTL, 30 | ): Promise => { 31 | await kv.set(cacheKey, value, { ex: ttl }); 32 | }; 33 | -------------------------------------------------------------------------------- /apps/web/src/app/api/trpc/[trpc]/route.ts: -------------------------------------------------------------------------------- 1 | import { createTRPCContext } from "@/trpc/init"; 2 | import { appRouter } from "@/trpc/routers/_app"; 3 | import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; 4 | import type { NextRequest } from "next/server"; 5 | 6 | /** 7 | * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when 8 | * handling an HTTP request (e.g. when you make requests from Client Components). 9 | */ 10 | const createContext = async (req: NextRequest) => { 11 | return createTRPCContext({ 12 | headers: req.headers, 13 | }); 14 | }; 15 | 16 | const handler = (req: NextRequest) => 17 | fetchRequestHandler({ 18 | endpoint: "/api/trpc", 19 | req, 20 | router: appRouter, 21 | createContext: async () => await createContext(req), 22 | onError: 23 | process.env.NODE_ENV === "development" 24 | ? ({ path, error }) => { 25 | console.error( 26 | `❌ tRPC failed on ${path ?? ""}: ${error.message}`, 27 | ); 28 | } 29 | : undefined, 30 | }); 31 | 32 | export { handler as GET, handler as POST }; 33 | -------------------------------------------------------------------------------- /apps/web/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/src/app/favicon.ico -------------------------------------------------------------------------------- /apps/web/src/components/copy-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Check, Copy } from "lucide-react"; 4 | import { useEffect, useState } from "react"; 5 | 6 | export function CopyButton({ code }: { code: string }) { 7 | const [copied, setCopied] = useState(false); 8 | 9 | useEffect(() => { 10 | if (copied) { 11 | const timeout = setTimeout(() => setCopied(false), 2000); 12 | return () => clearTimeout(timeout); 13 | } 14 | }, [copied]); 15 | 16 | const copyToClipboard = async () => { 17 | try { 18 | await navigator.clipboard.writeText(code); 19 | setCopied(true); 20 | } catch (err) { 21 | console.error("Failed to copy code:", err); 22 | } 23 | }; 24 | 25 | return ( 26 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /apps/web/src/components/dotted-separator.tsx: -------------------------------------------------------------------------------- 1 | export function DottedSeparator() { 2 | return
; 3 | } 4 | -------------------------------------------------------------------------------- /apps/web/src/components/footer.tsx: -------------------------------------------------------------------------------- 1 | import { DottedSeparator } from "./dotted-separator"; 2 | import { FooterLogo } from "./footer-logo"; 3 | import { GetStarted } from "./get-started"; 4 | 5 | export function Footer() { 6 | return ( 7 |
8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/src/components/icons.tsx: -------------------------------------------------------------------------------- 1 | export const Icons = { 2 | Tune: ({ 3 | size = 24, 4 | ...props 5 | }: { size?: number } & React.SVGProps) => { 6 | return ( 7 | 15 | 16 | 17 | ); 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /apps/web/src/components/modals/index.tsx: -------------------------------------------------------------------------------- 1 | import { ChangePlanModal } from "./change-plan"; 2 | import { CreateProjectModal } from "./create-project"; 3 | import { CreateTeamModal } from "./create-team"; 4 | import { InviteModal } from "./invite"; 5 | 6 | export function GlobalModals() { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/src/components/onboarding-steps.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTranslations } from "next-intl"; 4 | import Step1 from "./onboarding/step-1"; 5 | import Step2 from "./onboarding/step-2"; 6 | 7 | export function OnboardingSteps({ projectId }: { projectId: string }) { 8 | const t = useTranslations("onboarding"); 9 | 10 | return ( 11 |
12 |
13 |
14 | 15 | 16 | 17 | 18 |

19 | {t("info.description")}{" "} 20 | 26 | {t("info.link")} 27 | {" "} 28 | {t("info.description_2")} 29 |

30 |
31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /apps/web/src/components/period-selector.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | Select, 5 | SelectContent, 6 | SelectItem, 7 | SelectTrigger, 8 | } from "@/components/ui/select"; 9 | import { usePeriod } from "@/hooks/use-period"; 10 | import { periods } from "@/hooks/use-period"; 11 | import { useTranslations } from "next-intl"; 12 | 13 | export function PeriodSelector() { 14 | const { period, setPeriod } = usePeriod(); 15 | const t = useTranslations("periods"); 16 | 17 | return ( 18 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /apps/web/src/components/settings/billing.tsx: -------------------------------------------------------------------------------- 1 | import { trpc } from "@/trpc/client"; 2 | import { useParams } from "next/navigation"; 3 | import { BillingPlan, BillingPlanSkeleton } from "../billing-plan"; 4 | 5 | export function BillingSettings() { 6 | const { organization } = useParams(); 7 | 8 | const [data, { isPending }] = trpc.organization.getStats.useSuspenseQuery({ 9 | organizationId: organization as string, 10 | }); 11 | 12 | if (isPending) { 13 | return ; 14 | } 15 | 16 | return ( 17 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/src/components/sheets/index.tsx: -------------------------------------------------------------------------------- 1 | import { OverridesSheet } from "./overrides"; 2 | 3 | export function GlobalSheets() { 4 | return ( 5 | <> 6 | 7 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/src/components/sign-in.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Link } from "@/i18n/routing"; 4 | import { useTranslations } from "next-intl"; 5 | 6 | export function SignIn() { 7 | const t = useTranslations("header"); 8 | 9 | return {t("signIn")}; 10 | } 11 | -------------------------------------------------------------------------------- /apps/web/src/components/team-selector.server.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@/components/ui/skeleton"; 2 | import { HydrateClient, trpc } from "@/trpc/server"; 3 | import { Suspense } from "react"; 4 | import { TeamSelector } from "./team-selector"; 5 | 6 | export async function TeamSelectorServer() { 7 | trpc.organization.getAll.prefetch(); 8 | 9 | return ( 10 | 11 | }> 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 4 | import { Check } from "lucide-react"; 5 | import * as React from "react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const Checkbox = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | 24 | 25 | 26 | 27 | )); 28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName; 29 | 30 | export { Checkbox }; 31 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ); 18 | }, 19 | ); 20 | Input.displayName = "Input"; 21 | 22 | export { Input }; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as LabelPrimitive from "@radix-ui/react-label"; 4 | import { type VariantProps, cva } from "class-variance-authority"; 5 | import * as React from "react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const labelVariants = cva( 10 | "text-xs text-secondary leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 11 | ); 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )); 24 | Label.displayName = LabelPrimitive.Root.displayName; 25 | 26 | export { Label }; 27 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/loader.tsx: -------------------------------------------------------------------------------- 1 | const bars = Array(12).fill(0); 2 | 3 | export const Loader = ({ size = 16 }) => { 4 | return ( 5 |
6 |
12 |
13 | {bars.map((_, i) => ( 14 |
15 | ))} 16 |
17 |
18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as ProgressPrimitive from "@radix-ui/react-progress"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Progress = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, value, ...props }, ref) => ( 12 | 20 | 24 | 25 | )); 26 | Progress.displayName = ProgressPrimitive.Root.displayName; 27 | 28 | export { Progress }; 29 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref, 15 | ) => ( 16 | 27 | ), 28 | ); 29 | Separator.displayName = SeparatorPrimitive.Root.displayName; 30 | 31 | export { Separator }; 32 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return
; 8 | } 9 | 10 | export { Skeleton }; 11 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as SliderPrimitive from "@radix-ui/react-slider"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Slider = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 21 | 22 | 23 | 24 | 25 | )); 26 | Slider.displayName = SliderPrimitive.Root.displayName; 27 | 28 | export { Slider }; 29 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTheme } from "next-themes"; 4 | import { Toaster as Sonner } from "sonner"; 5 | 6 | type ToasterProps = React.ComponentProps; 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = "system" } = useTheme(); 10 | 11 | return ( 12 | 28 | ); 29 | }; 30 | 31 | export { Toaster }; 32 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/spinner.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | interface SpinnerProps { 6 | className?: string; 7 | size?: "sm" | "md" | "lg"; 8 | } 9 | 10 | export function Spinner({ className, size = "md" }: SpinnerProps) { 11 | const sizeClasses = { 12 | sm: "h-4 w-4", 13 | md: "h-6 w-6", 14 | lg: "h-8 w-8", 15 | }; 16 | 17 | return ( 18 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/submit-button.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { Loader2 } from "lucide-react"; 3 | import { Button, type ButtonProps } from "./button"; 4 | 5 | export function SubmitButton({ 6 | children, 7 | isSubmitting, 8 | disabled, 9 | ...props 10 | }: { 11 | children: React.ReactNode; 12 | isSubmitting: boolean; 13 | disabled?: boolean; 14 | } & ButtonProps) { 15 | return ( 16 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<"textarea"> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |