├── .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 |
28 |
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 |
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 |
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 |
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 |
18 | );
19 | });
20 | Textarea.displayName = "Textarea";
21 |
22 | export { Textarea };
23 |
--------------------------------------------------------------------------------
/apps/web/src/contexts/session.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { Session } from "@supabase/supabase-js";
4 | import { type ReactNode, createContext, useContext } from "react";
5 |
6 | interface SessionContextType {
7 | session: Session | null;
8 | }
9 |
10 | const SessionContext = createContext(undefined);
11 |
12 | export function SessionProvider({
13 | children,
14 | session,
15 | }: {
16 | children: ReactNode;
17 | session: Session | null;
18 | }) {
19 | return (
20 |
21 | {children}
22 |
23 | );
24 | }
25 |
26 | export function useSession() {
27 | const context = useContext(SessionContext);
28 |
29 | if (context === undefined) {
30 | throw new Error("useSession must be used within a SessionProvider");
31 | }
32 |
33 | return context;
34 | }
35 |
--------------------------------------------------------------------------------
/apps/web/src/db/primary.ts:
--------------------------------------------------------------------------------
1 | import { drizzle } from "drizzle-orm/postgres-js";
2 | import postgres from "postgres";
3 | import * as schema from "./schema";
4 |
5 | export const primaryDb = drizzle(
6 | postgres(process.env.DATABASE_PRIMARY_URL!, { prepare: false }),
7 | { schema },
8 | );
9 |
--------------------------------------------------------------------------------
/apps/web/src/emails/components/logo.tsx:
--------------------------------------------------------------------------------
1 | import { getAppUrl } from "@/lib/url";
2 | import { Img, Section } from "@react-email/components";
3 |
4 | const appUrl = getAppUrl();
5 |
6 | export function Logo() {
7 | return (
8 |
9 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/apps/web/src/emails/components/outline-button.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@react-email/components";
2 |
3 | export function OutlineButton({
4 | children,
5 | className,
6 | variant = "default",
7 | href,
8 | }: {
9 | children: React.ReactNode;
10 | className?: string;
11 | variant?: "default" | "secondary";
12 | href?: string;
13 | }) {
14 | const isDefault = variant === "default";
15 | const backgroundColor = isDefault ? "#000000" : "#FFFFFF";
16 | const textColor = isDefault ? "#FFFFFF" : "#000000";
17 |
18 | return (
19 |
20 |
24 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/use-create-project-modal.ts:
--------------------------------------------------------------------------------
1 | import { parseAsBoolean, useQueryState } from "nuqs";
2 |
3 | export function useCreateProjectModal() {
4 | const [open, setOpen] = useQueryState(
5 | "create-project",
6 | parseAsBoolean.withDefault(false),
7 | );
8 |
9 | return {
10 | open,
11 | setOpen,
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/use-create-team-modal.ts:
--------------------------------------------------------------------------------
1 | import { parseAsBoolean, useQueryState } from "nuqs";
2 |
3 | export function useCreateTeamModal() {
4 | const [open, setOpen] = useQueryState(
5 | "create-team",
6 | parseAsBoolean.withDefault(false),
7 | );
8 |
9 | return {
10 | open,
11 | setOpen,
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/use-filters.ts:
--------------------------------------------------------------------------------
1 | import { parseAsArrayOf, parseAsString, useQueryStates } from "nuqs";
2 |
3 | export function useFilters() {
4 | const [{ locales }, setQueryStates] = useQueryStates({
5 | locales: parseAsArrayOf(parseAsString).withDefault([]),
6 | });
7 |
8 | const setSelectedLocales = (newLocales: string[]) => {
9 | setQueryStates({ locales: newLocales.length ? newLocales : null });
10 | };
11 |
12 | return {
13 | selectedLocales: locales,
14 | setSelectedLocales,
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/use-invite-modal.ts:
--------------------------------------------------------------------------------
1 | import { parseAsBoolean, useQueryState } from "nuqs";
2 |
3 | export function useInviteModal() {
4 | const [open, setOpen] = useQueryState(
5 | "invite",
6 | parseAsBoolean.withDefault(false),
7 | );
8 |
9 | return {
10 | open,
11 | setOpen,
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/use-media-query.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export function useMediaQuery(query: string): boolean {
4 | const [matches, setMatches] = useState(false);
5 |
6 | useEffect(() => {
7 | const mediaQuery = window.matchMedia(query);
8 | setMatches(mediaQuery.matches);
9 |
10 | const listener = (e: MediaQueryListEvent) => {
11 | setMatches(e.matches);
12 | };
13 |
14 | mediaQuery.addEventListener("change", listener);
15 |
16 | return () => {
17 | mediaQuery.removeEventListener("change", listener);
18 | };
19 | }, [query]);
20 |
21 | return matches;
22 | }
23 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/use-mobile.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const MOBILE_BREAKPOINT = 768;
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = React.useState(
7 | undefined,
8 | );
9 |
10 | React.useEffect(() => {
11 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
12 | const onChange = () => {
13 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
14 | };
15 | mql.addEventListener("change", onChange);
16 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17 | return () => mql.removeEventListener("change", onChange);
18 | }, []);
19 |
20 | return !!isMobile;
21 | }
22 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/use-overrides-sheet.ts:
--------------------------------------------------------------------------------
1 | import { parseAsString, useQueryStates } from "nuqs";
2 |
3 | export function useOverridesSheet() {
4 | const [{ translationKey, projectId, locale }, setQueryStates] =
5 | useQueryStates({
6 | translationKey: parseAsString.withDefault(""),
7 | projectId: parseAsString.withDefault(""),
8 | locale: parseAsString.withDefault(""),
9 | });
10 |
11 | return {
12 | translationKey,
13 | projectId,
14 | locale,
15 | setQueryStates,
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/use-period.ts:
--------------------------------------------------------------------------------
1 | import { parseAsStringLiteral, useQueryState } from "nuqs";
2 | import { useCallback } from "react";
3 |
4 | export const periods = ["daily", "weekly", "monthly"] as const;
5 | export type Period = (typeof periods)[number];
6 |
7 | export function usePeriod() {
8 | const [period, setPeriod] = useQueryState(
9 | "period",
10 | parseAsStringLiteral(periods).withDefault("daily"),
11 | );
12 |
13 | const handlePeriodChange = useCallback(
14 | (value: Period) => {
15 | setPeriod(value || "daily");
16 | },
17 | [setPeriod],
18 | );
19 |
20 | return {
21 | period: period || "daily",
22 | setPeriod: handlePeriodChange,
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/use-plan-modal.tsx:
--------------------------------------------------------------------------------
1 | import { parseAsInteger, parseAsString, useQueryStates } from "nuqs";
2 |
3 | export function usePlanModal() {
4 | const [{ tier, modal }, setQueryStates] = useQueryStates({
5 | tier: parseAsInteger.withDefault(0),
6 | modal: parseAsString.withDefault(""),
7 | });
8 |
9 | return {
10 | tier,
11 | open: modal === "plan",
12 | setQueryStates,
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/use-search.ts:
--------------------------------------------------------------------------------
1 | import { useQueryState } from "nuqs";
2 | import { useCallback } from "react";
3 |
4 | export function useSearch() {
5 | const [search, setSearch] = useQueryState("q");
6 |
7 | const handleSearch = useCallback(
8 | (value: string) => {
9 | setSearch(value || null);
10 | },
11 | [setSearch],
12 | );
13 |
14 | return {
15 | search: search || null,
16 | setSearch: handleSearch,
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/apps/web/src/i18n/request.ts:
--------------------------------------------------------------------------------
1 | import { getRequestConfig } from "next-intl/server";
2 | import { routing } from "./routing";
3 |
4 | export default getRequestConfig(async ({ requestLocale }) => {
5 | let locale = await requestLocale;
6 |
7 | if (
8 | !locale ||
9 | !routing.locales.includes(locale as (typeof routing.locales)[number])
10 | ) {
11 | locale = routing.defaultLocale;
12 | }
13 |
14 | return {
15 | locale,
16 | messages: (await import(`../messages/${locale}.json`)).default,
17 | };
18 | });
19 |
--------------------------------------------------------------------------------
/apps/web/src/i18n/routing.ts:
--------------------------------------------------------------------------------
1 | import languineConfig from "languine.json";
2 | import { createNavigation } from "next-intl/navigation";
3 | import { defineRouting } from "next-intl/routing";
4 |
5 | export const routing = defineRouting({
6 | locales: [...languineConfig.locale.targets, languineConfig.locale.source],
7 | defaultLocale: languineConfig.locale.source,
8 | });
9 |
10 | export const { Link, redirect, usePathname, useRouter, getPathname } =
11 | createNavigation(routing);
12 |
--------------------------------------------------------------------------------
/apps/web/src/jobs/utils/chunk.ts:
--------------------------------------------------------------------------------
1 | import { estimateTokensForContent } from "./tokeniser";
2 | import type { PromptOptions } from "./types";
3 |
4 | export function calculateChunkSize(
5 | content: Array<{ key: string; sourceText: string }>,
6 | options?: PromptOptions,
7 | ) {
8 | const MAX_INPUT_TOKENS = 128000;
9 | const MAX_OUTPUT_TOKENS = 16000;
10 | const MIN_CHUNK_SIZE = 1;
11 | const MAX_CHUNK_SIZE = 100;
12 |
13 | if (content.length === 0) {
14 | return MIN_CHUNK_SIZE;
15 | }
16 |
17 | const estimatedTokens = estimateTokensForContent(content, options);
18 |
19 | // Calculate how many items we can fit in a chunk based on input token limit
20 | // Account for both input and output token limits
21 | const itemsPerChunk = Math.min(
22 | MAX_CHUNK_SIZE,
23 | Math.max(
24 | MIN_CHUNK_SIZE,
25 | Math.floor(
26 | ((MAX_INPUT_TOKENS - MAX_OUTPUT_TOKENS) / estimatedTokens) *
27 | content.length,
28 | ),
29 | ),
30 | );
31 |
32 | return itemsPerChunk;
33 | }
34 |
--------------------------------------------------------------------------------
/apps/web/src/jobs/utils/locale.ts:
--------------------------------------------------------------------------------
1 | export function getLanguageName(locale: string) {
2 | try {
3 | const displayNames = new Intl.DisplayNames(["en"], { type: "language" });
4 | const languageCode = locale.split("-")[0];
5 |
6 | return displayNames.of(languageCode) || locale;
7 | } catch (error) {
8 | return locale;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/apps/web/src/jobs/utils/tokeniser.ts:
--------------------------------------------------------------------------------
1 | import { createFinalPrompt } from "./prompt";
2 | import type { PromptOptions } from "./types";
3 |
4 | /**
5 | * Estimates the number of tokens required for a given content and prompt options.
6 | *
7 | * @param content - An array of objects containing a key and source text.
8 | * @param options - Optional prompt options.
9 | * @returns The estimated number of tokens.
10 | */
11 | export function estimateTokensForContent(
12 | content: Array<{ key: string; sourceText: string }>,
13 | options?: PromptOptions,
14 | ) {
15 | // Calculate tokens based on character count (rough estimation)
16 | const contentTokens = content.reduce(
17 | (sum, item) => sum + Math.ceil(item.sourceText.length / 4),
18 | 0,
19 | );
20 |
21 | // Add estimated prompt tokens if options are provided
22 | let promptTokens = 0;
23 | if (options) {
24 | const prompt = createFinalPrompt(content, options);
25 | promptTokens = Math.ceil(prompt.length / 4);
26 | }
27 |
28 | return contentTokens + promptTokens;
29 | }
30 |
--------------------------------------------------------------------------------
/apps/web/src/jobs/utils/types.ts:
--------------------------------------------------------------------------------
1 | import type { projectSettings } from "@/db/schema";
2 |
3 | export type PromptOptions = {
4 | sourceLocale: string;
5 | targetLocale: string;
6 | sourceFormat?: string;
7 | settings?: Partial;
8 | };
9 |
--------------------------------------------------------------------------------
/apps/web/src/lib/auth/cli.ts:
--------------------------------------------------------------------------------
1 | import { kv } from "@/lib/kv";
2 | import type { Session } from "@supabase/supabase-js";
3 |
4 | export const CLI_TOKEN_NAME = "cli-token";
5 |
6 | export async function saveCLISession(session: Session, token: string) {
7 | await kv.set(`${CLI_TOKEN_NAME}:${token}`, session, {
8 | ex: 5 * 60,
9 | });
10 | }
11 |
12 | export async function getCLISession(token: string) {
13 | const data = await kv.get(`${CLI_TOKEN_NAME}:${token}`);
14 |
15 | return data;
16 | }
17 |
--------------------------------------------------------------------------------
/apps/web/src/lib/format.ts:
--------------------------------------------------------------------------------
1 | export const formatTimeAgo = (date: Date) => {
2 | const now = new Date();
3 | const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
4 |
5 | if (diffInSeconds < 30) return "just now";
6 | if (diffInSeconds < 60) return `${diffInSeconds}s`;
7 | if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m`;
8 | if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h`;
9 | if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 86400)}d`;
10 | if (diffInSeconds < 31536000)
11 | return `${Math.floor(diffInSeconds / 2592000)}mo`;
12 | return `${Math.floor(diffInSeconds / 31536000)}y`;
13 | };
14 |
15 | export function displayLanguageName(locale: string) {
16 | try {
17 | return new Intl.DisplayNames(["en"], {
18 | type: "language",
19 | }).of(locale);
20 | } catch (error) {
21 | return locale;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/web/src/lib/kv.ts:
--------------------------------------------------------------------------------
1 | import { Redis } from "@upstash/redis";
2 |
3 | export const kv = new Redis({
4 | url: process.env.UPSTASH_REDIS_REST_URL,
5 | token: process.env.UPSTASH_REDIS_REST_TOKEN,
6 | });
7 |
--------------------------------------------------------------------------------
/apps/web/src/lib/polar.ts:
--------------------------------------------------------------------------------
1 | import { Polar } from "@polar-sh/sdk";
2 |
3 | export const api = new Polar({
4 | accessToken: process.env.POLAR_ACCESS_TOKEN!,
5 | server: process.env.NEXT_PUBLIC_POLAR_ENVIRONMENT as "production" | "sandbox",
6 | });
7 |
--------------------------------------------------------------------------------
/apps/web/src/lib/resend.ts:
--------------------------------------------------------------------------------
1 | import { Resend } from "resend";
2 |
3 | export const resend = new Resend(process.env.RESEND_API_KEY);
4 |
--------------------------------------------------------------------------------
/apps/web/src/lib/schemas/support.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const supportFormSchema = z.object({
4 | severity: z.enum(["low", "medium", "high"], {
5 | required_error: "Please select a severity level",
6 | }),
7 | description: z.string().min(10, {
8 | message: "Description must be at least 10 characters.",
9 | }),
10 | });
11 |
12 | export const supportRequestSchema = supportFormSchema.extend({
13 | name: z.string(),
14 | email: z.string().email(),
15 | projectId: z.string(),
16 | organizationId: z.string(),
17 | });
18 |
19 | export type SupportFormData = z.infer;
20 | export type SupportRequest = z.infer;
21 |
--------------------------------------------------------------------------------
/apps/web/src/lib/url.ts:
--------------------------------------------------------------------------------
1 | export function getAppUrl() {
2 | if (process.env.NODE_ENV !== "production") {
3 | return "http://localhost:3000";
4 | }
5 |
6 | return "https://languine.ai";
7 | }
8 |
--------------------------------------------------------------------------------
/apps/web/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/android.mdx:
--------------------------------------------------------------------------------
1 | # Android
2 |
3 | Languine supports Android string resources (strings.xml) as a source format, handling plurals, string arrays, and formatted strings. This format is the standard way to manage translations in Android applications.
4 |
5 | ---
6 |
7 | ## Setting Up
8 |
9 | First, make sure you've got a languine.json config file in your project root. Here's an example:
10 |
11 | ```json
12 | {
13 | "locale": {
14 | "source": "en",
15 | "targets": ["sv", "de", "fr"]
16 | },
17 | "files": {
18 | "android": {
19 | "include": ["app/src/main/res/values-[locale]/strings.xml"]
20 | }
21 | }
22 | }
23 | ```
24 |
25 | ## Translating
26 |
27 | With your config set, run:
28 |
29 | ```bash
30 | npx languine@latest translate
31 | ```
32 |
33 | When you run this command, Languine will:
34 |
35 | - Load your source strings.xml file (e.g., values/strings.xml)
36 | - Detect any new or modified translation strings
37 | - Generate translations for your target languages
38 | - Create or update the target language XML files (e.g., values-sv/strings.xml, values-de/strings.xml, values-fr/strings.xml)
39 | - Preserve any existing string arrays and plurals formatting
40 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/arb.mdx:
--------------------------------------------------------------------------------
1 | # ARB (Application Resource Bundle)
2 |
3 | Languine supports ARB files, the standard localization format for Flutter applications. This format handles plurals, placeholders, and select expressions while maintaining compatibility with Flutter's intl package.
4 |
5 | ---
6 |
7 | ## Setting Up
8 |
9 | First, make sure you've got a languine.json config file in your project root. Here's an example:
10 |
11 | ```json
12 | {
13 | "locale": {
14 | "source": "en",
15 | "targets": ["sv", "de", "fr"]
16 | },
17 | "files": {
18 | "arb": {
19 | "include": ["lib/l10n/intl_[locale].arb"]
20 | }
21 | }
22 | }
23 | ```
24 |
25 | ## Translating
26 |
27 | With your config set, run:
28 |
29 | ```bash
30 | npx languine@latest translate
31 | ```
32 |
33 | When you run this command, Languine will:
34 |
35 | - Load your source ARB file (e.g., lib/l10n/intl_en.arb)
36 | - Detect any new or modified translation strings
37 | - Generate translations for your target languages
38 | - Create or update the target language ARB files (e.g., lib/l10n/intl_sv.arb)
39 | - Preserve metadata, placeholders, and plural/select expressions
40 |
41 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/biome.mdx:
--------------------------------------------------------------------------------
1 | # Biome
2 |
3 | Languine can work together with [Biome](https://biomejs.dev/) to ensure your translation files are consistently formatted. This is particularly useful when working with JSON translation files that need to maintain a consistent style across your project.
4 |
5 | ## Using with Languine
6 |
7 | The recommended way to use Biome with Languine is to format your translation files after they're generated. You can set this up in your `package.json`:
8 |
9 | ```json title="package.json"
10 | {
11 | "scripts": {
12 | "translate": "languine translate",
13 | "format": "biome format \"locales/**/*.json\"",
14 | "translate:format": "npm run translate && npm run format"
15 | }
16 | }
17 | ```
18 |
19 | Now you can:
20 | - Run `npm run translate` to only generate translations
21 | - Run `npm run format` to only format existing translation files
22 | - Run `npm run translate:format` to generate translations and format them in sequence
23 |
24 | This configuration will ensure your translation files are formatted consistently with 2-space indentation and a maximum line width of 80 characters.
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/csv.mdx:
--------------------------------------------------------------------------------
1 | # CSV
2 |
3 | Languine supports CSV (Comma-Separated Values) files for simple translation management. This format is ideal for spreadsheet-based workflows and easy integration with external translation tools.
4 |
5 | ---
6 |
7 | ## Setting Up
8 |
9 | First, make sure you've got a languine.json config file in your project root. Here's an example:
10 |
11 | ```json
12 | {
13 | "locale": {
14 | "source": "en",
15 | "targets": ["sv", "de", "fr"]
16 | },
17 | "files": {
18 | "csv": {
19 | "include": ["translations/[locale].csv"]
20 | }
21 | }
22 | }
23 | ```
24 |
25 | ## Translating
26 |
27 | With your config set, run:
28 |
29 | ```bash
30 | npx languine@latest translate
31 | ```
32 |
33 | When you run this command, Languine will:
34 |
35 | - Load your source CSV file (e.g., translations/en.csv)
36 | - Detect any new or modified translation strings
37 | - Generate translations for your target languages
38 | - Create or update the target language CSV files (e.g., translations/sv.csv)
39 | - Maintain column structure and formatting
40 | - Preserve any additional metadata columns
41 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/html.mdx:
--------------------------------------------------------------------------------
1 | # HTML
2 |
3 | Languine supports HTML file localization, handling both text content and attributes. This format is ideal for static websites and HTML templates, with support for preserving HTML structure and formatting.
4 |
5 | ---
6 |
7 | ## Setting Up
8 |
9 | First, make sure you've got a languine.json config file in your project root. Here's an example:
10 |
11 | ```json
12 | {
13 | "locale": {
14 | "source": "en",
15 | "targets": ["sv", "de", "fr"]
16 | },
17 | "files": {
18 | "html": {
19 | "include": ["pages/[locale]/*.html"]
20 | }
21 | }
22 | }
23 | ```
24 |
25 | ## Translating
26 |
27 | With your config set, run:
28 |
29 | ```bash
30 | npx languine@latest translate
31 | ```
32 |
33 | When you run this command, Languine will:
34 |
35 | - Load your source HTML files (e.g., pages/en/*.html)
36 | - Detect any new or modified translation strings
37 | - Generate translations for your target languages
38 | - Create or update the target language HTML files (e.g., pages/sv/*.html)
39 | - Preserve HTML structure and formatting
40 | - Handle specified attribute translations
41 | - Maintain comments and special tags
42 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/ios.mdx:
--------------------------------------------------------------------------------
1 | # iOS
2 |
3 | Languine supports iOS Localizable.strings files, the standard localization format for iOS applications. This format handles basic key-value string pairs and supports comments for providing context to translators.
4 |
5 | ---
6 |
7 | ## Setting Up
8 |
9 | First, make sure you've got a languine.json config file in your project root. Here's an example:
10 |
11 | ```json
12 | {
13 | "locale": {
14 | "source": "en",
15 | "targets": ["sv", "de", "fr"]
16 | },
17 | "files": {
18 | "ios": {
19 | "include": ["[locale].lproj/Localizable.strings"]
20 | }
21 | }
22 | }
23 | ```
24 |
25 | ## Translating
26 |
27 | With your config set, run:
28 |
29 | ```bash
30 | npx languine@latest translate
31 | ```
32 |
33 | When you run this command, Languine will:
34 |
35 | - Load your source Localizable.strings file (e.g., en.lproj/Localizable.strings)
36 | - Detect any new or modified translation strings
37 | - Generate translations for your target languages
38 | - Create or update the target language files (e.g., sv.lproj/Localizable.strings)
39 | - Preserve any existing comments and formatting
40 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/js.mdx:
--------------------------------------------------------------------------------
1 | # JavaScript
2 |
3 | Languine supports JavaScript/TypeScript localization files, perfect for modern web applications. This format handles both object literals and module exports, supporting nested structures and dynamic content.
4 |
5 | ---
6 |
7 | ## Setting Up
8 |
9 | First, make sure you've got a languine.json config file in your project root. Here's an example:
10 |
11 | ```json
12 | {
13 | "locale": {
14 | "source": "en",
15 | "targets": ["sv", "de", "fr"]
16 | },
17 | "files": {
18 | "js": {
19 | "include": ["src/locales/[locale].js"]
20 | }
21 | }
22 | }
23 | ```
24 |
25 | ## Translating
26 |
27 | With your config set, run:
28 |
29 | ```bash
30 | npx languine@latest translate
31 | ```
32 |
33 | When you run this command, Languine will:
34 |
35 | - Load your source JavaScript files (e.g., src/locales/en.js)
36 | - Detect any new or modified translation strings
37 | - Generate translations for your target languages
38 | - Create or update the target language files (e.g., src/locales/sv.js)
39 | - Preserve module structure and exports
40 | - Handle nested objects and arrays
41 | - Maintain code formatting and comments
42 | - Generate TypeScript types if enabled
43 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/json.mdx:
--------------------------------------------------------------------------------
1 | # JSON
2 |
3 | Languine supports JSON files as a source format with the common requirements for pluralization and interpolation. This format is used in many frameworks like React, Vue, Ruby on Rails, and more.
4 |
5 |
6 | ---
7 |
8 |
9 | ## Setting Up
10 |
11 | First, make sure you've got a languine.json config file in your project root. Here's an example:
12 |
13 | ```json
14 | {
15 | "locale": {
16 | "source": "en",
17 | "targets": ["sv", "de", "fr"]
18 | },
19 | "files": {
20 | "json": {
21 | "include": ["locales/[locale].json"]
22 | }
23 | }
24 | }
25 |
26 | ```
27 |
28 | ## Translating
29 |
30 | With your config set, run:
31 |
32 | ```bash
33 | npx languine@latest translate
34 | ```
35 |
36 | When you run this command, Languine will:
37 |
38 | - Load your source JSON file (e.g., locales/en.json)
39 | - Detect any new or modified translation strings
40 | - Generate translations for your target languages
41 | - Create or update the target language JSON files (e.g., locales/sv.json, locales/de.json, locales/fr.json)
42 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/overrides.mdx:
--------------------------------------------------------------------------------
1 | # Overrides
2 |
3 | Languine allows you to override translations for specific keys. This is useful when you want to ensure that a specific translation is used for a particular key, regardless of the default translation.
4 |
5 | ## How it works
6 |
7 | In the dashboard, you can create overrides for specific keys. You can also create overrides for specific languages.
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/prettier.mdx:
--------------------------------------------------------------------------------
1 | # Prettier
2 |
3 | Languine can work together with [Prettier](https://prettier.io/) to ensure your translation files are consistently formatted. This is particularly useful when working with JSON translation files that need to maintain a consistent style across your project.
4 |
5 | ## Using with Languine
6 |
7 | The recommended way to use Prettier with Languine is to format your translation files after they're generated. You can set this up in your `package.json`:
8 |
9 | ```json title="package.json"
10 | {
11 | "scripts": {
12 | "translate": "languine translate",
13 | "format": "prettier --write \"locales/**/*.json\"",
14 | "translate:format": "npm run translate && npm run format"
15 | }
16 | }
17 | ```
18 |
19 | Now you can:
20 | - Run `npm run translate` to only generate translations
21 | - Run `npm run format` to only format existing translation files
22 | - Run `npm run translate:format` to generate translations and format them in sequence
23 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/properties.mdx:
--------------------------------------------------------------------------------
1 | # Properties
2 |
3 | Languine supports Java Properties files, the standard localization format for Java applications. This format handles Unicode escaping and supports both key-value pairs and comments.
4 |
5 | ---
6 |
7 | ## Setting Up
8 |
9 | First, make sure you've got a languine.json config file in your project root. Here's an example:
10 |
11 | ```json
12 | {
13 | "locale": {
14 | "source": "en",
15 | "targets": ["sv", "de", "fr"]
16 | },
17 | "files": {
18 | "properties": {
19 | "include": ["src/main/resources/messages_[locale].properties"]
20 | }
21 | }
22 | }
23 | ```
24 |
25 | ## Translating
26 |
27 | With your config set, run:
28 |
29 | ```bash
30 | npx languine@latest translate
31 | ```
32 |
33 | When you run this command, Languine will:
34 |
35 | - Load your source properties file (e.g., messages_en.properties)
36 | - Detect any new or modified translation strings
37 | - Generate translations for your target languages
38 | - Create or update the target language properties files (e.g., messages_sv.properties)
39 | - Handle Unicode escaping automatically
40 | - Preserve comments and formatting
41 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/settings.mdx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/src/markdown/docs/en/settings.mdx
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/tuning.mdx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/apps/web/src/markdown/docs/en/tuning.mdx
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/xliff.mdx:
--------------------------------------------------------------------------------
1 | # XLIFF
2 |
3 | Languine supports XLIFF (XML Localization Interchange File Format), an XML-based format designed for exchanging localization data. This format is widely used in professional translation workflows and supports both XLIFF 1.2 and 2.0 versions.
4 |
5 | ---
6 |
7 | ## Setting Up
8 |
9 | First, make sure you've got a languine.json config file in your project root. Here's an example:
10 |
11 | ```json
12 | {
13 | "locale": {
14 | "source": "en",
15 | "targets": ["sv", "de", "fr"]
16 | },
17 | "files": {
18 | "xliff": {
19 | "include": ["locales/[locale].xlf"]
20 | }
21 | }
22 | }
23 | ```
24 |
25 | ## Translating
26 |
27 | With your config set, run:
28 |
29 | ```bash
30 | npx languine@latest translate
31 | ```
32 |
33 | When you run this command, Languine will:
34 |
35 | - Load your source XLIFF file (e.g., locales/en.xlf)
36 | - Detect any new or modified translation units
37 | - Generate translations for your target languages
38 | - Create or update the target language XLIFF files (e.g., locales/sv.xlf)
39 | - Preserve notes, context information, and state attributes
40 | - Maintain compatibility with CAT (Computer-Aided Translation) tools
41 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/xml.mdx:
--------------------------------------------------------------------------------
1 | # XML
2 |
3 | Languine supports XML file localization, suitable for various XML-based configuration and content files. This format handles complex XML structures while preserving formatting and attributes.
4 |
5 | ---
6 |
7 | ## Setting Up
8 |
9 | First, make sure you've got a languine.json config file in your project root. Here's an example:
10 |
11 | ```json
12 | {
13 | "locale": {
14 | "source": "en",
15 | "targets": ["sv", "de", "fr"]
16 | },
17 | "files": {
18 | "xml": {
19 | "include": ["resources/[locale]/*.xml"]
20 | }
21 | }
22 | }
23 | ```
24 |
25 | ## Translating
26 |
27 | With your config set, run:
28 |
29 | ```bash
30 | npx languine@latest translate
31 | ```
32 |
33 | When you run this command, Languine will:
34 |
35 | - Load your source XML files (e.g., resources/en/*.xml)
36 | - Detect any new or modified content
37 | - Generate translations for your target languages
38 | - Create or update the target language files (e.g., resources/sv/*.xml)
39 | - Preserve XML structure and formatting
40 | - Handle attribute translations
41 | - Maintain CDATA sections
42 | - Keep XML comments
43 | - Support XML namespaces
44 | - Validate XML structure
45 |
--------------------------------------------------------------------------------
/apps/web/src/markdown/docs/en/yaml.mdx:
--------------------------------------------------------------------------------
1 | # YAML
2 |
3 | Languine supports YAML files as a source format, which is commonly used in Ruby on Rails, Laravel, and other frameworks. This format supports nested structures, arrays, and complex data types.
4 |
5 | ---
6 |
7 | ## Setting Up
8 |
9 | First, make sure you've got a languine.json config file in your project root. Here's an example:
10 |
11 | ```json
12 | {
13 | "locale": {
14 | "source": "en",
15 | "targets": ["sv", "de", "fr"]
16 | },
17 | "files": {
18 | "yaml": {
19 | "include": ["config/locales/[locale].yml"]
20 | }
21 | }
22 | }
23 | ```
24 |
25 | ## Translating
26 |
27 | With your config set, run:
28 |
29 | ```bash
30 | npx languine@latest translate
31 | ```
32 |
33 | When you run this command, Languine will:
34 |
35 | - Load your source YAML file (e.g., config/locales/en.yml)
36 | - Detect any new or modified translation strings
37 | - Generate translations for your target languages
38 | - Create or update the target language YAML files (e.g., config/locales/sv.yml)
39 | - Preserve nested structures and formatting
40 |
--------------------------------------------------------------------------------
/apps/web/src/trpc/query-client.ts:
--------------------------------------------------------------------------------
1 | import {
2 | QueryClient,
3 | defaultShouldDehydrateQuery,
4 | } from "@tanstack/react-query";
5 | import superjson from "superjson";
6 |
7 | export function makeQueryClient() {
8 | return new QueryClient({
9 | defaultOptions: {
10 | queries: {
11 | staleTime: 30 * 1000,
12 | },
13 | dehydrate: {
14 | serializeData: superjson.serialize,
15 | shouldDehydrateQuery: (query) =>
16 | defaultShouldDehydrateQuery(query) ||
17 | query.state.status === "pending",
18 | },
19 | hydrate: {
20 | deserializeData: superjson.deserialize,
21 | },
22 | },
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/apps/web/src/trpc/routers/_app.ts:
--------------------------------------------------------------------------------
1 | import type { inferRouterOutputs } from "@trpc/server";
2 | import { createTRPCRouter } from "../init";
3 | import { analyticsRouter } from "./analytics";
4 | import { jobsRouter } from "./jobs";
5 | import { organizationRouter } from "./organization";
6 | import { projectRouter } from "./project";
7 | import { translateRouter } from "./translate";
8 | import { userRouter } from "./user";
9 |
10 | export const appRouter = createTRPCRouter({
11 | organization: organizationRouter,
12 | project: projectRouter,
13 | user: userRouter,
14 | translate: translateRouter,
15 | analytics: analyticsRouter,
16 | jobs: jobsRouter,
17 | });
18 |
19 | // export type definition of API
20 | export type AppRouter = typeof appRouter;
21 | export type RouterOutputs = inferRouterOutputs;
22 |
--------------------------------------------------------------------------------
/apps/web/src/trpc/routers/analytics.ts:
--------------------------------------------------------------------------------
1 | import { getAnalytics } from "@/db/queries/analytics";
2 | import { createTRPCRouter, protectedProcedure } from "../init";
3 | import { isOrganizationMember } from "../permissions/organization";
4 | import { analyticsSchema } from "./schema";
5 |
6 | export const analyticsRouter = createTRPCRouter({
7 | getProjectStats: protectedProcedure
8 | .input(analyticsSchema)
9 | .use(isOrganizationMember)
10 | .query(async ({ input }) => {
11 | const analytics = await getAnalytics({
12 | projectSlug: input.projectSlug,
13 | organizationId: input.organizationId,
14 | startDate: input.startDate,
15 | endDate: input.endDate,
16 | period: input.period,
17 | });
18 |
19 | return {
20 | data: analytics.data,
21 | totalKeys: analytics.totalKeys,
22 | totalLanguages: analytics.totalLanguages,
23 | period: analytics.period,
24 | };
25 | }),
26 | });
27 |
--------------------------------------------------------------------------------
/apps/web/src/trpc/routers/user.ts:
--------------------------------------------------------------------------------
1 | import {
2 | deleteUser,
3 | getUserById,
4 | updateUser,
5 | updateUserApiKey,
6 | } from "@/db/queries/user";
7 | import { z } from "zod";
8 | import { createTRPCRouter, protectedProcedure } from "../init";
9 |
10 | export const userRouter = createTRPCRouter({
11 | me: protectedProcedure.query(async ({ ctx }) => {
12 | return getUserById({ id: ctx.authenticatedId });
13 | }),
14 |
15 | update: protectedProcedure
16 | .input(
17 | z.object({
18 | name: z.string().optional(),
19 | email: z.string().email().optional(),
20 | }),
21 | )
22 | .mutation(async ({ input, ctx }) => {
23 | return updateUser({ id: ctx.authenticatedId, ...input });
24 | }),
25 |
26 | delete: protectedProcedure.mutation(async ({ ctx }) => {
27 | return deleteUser({ id: ctx.authenticatedId });
28 | }),
29 |
30 | updateApiKey: protectedProcedure.mutation(async ({ ctx }) => {
31 | return updateUserApiKey(ctx.authenticatedId);
32 | }),
33 | });
34 |
--------------------------------------------------------------------------------
/apps/web/src/trpc/server.ts:
--------------------------------------------------------------------------------
1 | import "server-only";
2 |
3 | import { createHydrationHelpers } from "@trpc/react-query/rsc";
4 | import { headers } from "next/headers";
5 | import { cache } from "react";
6 | import { createCallerFactory, createTRPCContext } from "./init";
7 | import { makeQueryClient } from "./query-client";
8 | import { type AppRouter, appRouter } from "./routers/_app";
9 |
10 | /**
11 | * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
12 | * handling a tRPC call from a React Server Component.
13 | */
14 | const createContext = cache(async () => {
15 | const heads = await headers();
16 | const headersObject = new Headers();
17 |
18 | for (const [key, value] of Array.from(heads.entries())) {
19 | headersObject.set(key, value);
20 | }
21 |
22 | headersObject.set("x-trpc-source", "rsc");
23 |
24 | return createTRPCContext({
25 | headers: headersObject,
26 | });
27 | });
28 |
29 | export const getQueryClient = cache(makeQueryClient);
30 |
31 | const caller = createCallerFactory(appRouter)(createContext);
32 |
33 | export const { trpc, HydrateClient } = createHydrationHelpers(
34 | caller,
35 | getQueryClient,
36 | );
37 |
--------------------------------------------------------------------------------
/apps/web/trigger.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "@trigger.dev/sdk/v3";
2 |
3 | export default defineConfig({
4 | project: "proj_rtlqllxxclkoghkeuwve",
5 | runtime: "node",
6 | logLevel: "log",
7 | maxDuration: 3600,
8 | retries: {
9 | enabledInDev: true,
10 | default: {
11 | maxAttempts: 3,
12 | minTimeoutInMs: 1000,
13 | maxTimeoutInMs: 10000,
14 | factor: 2,
15 | randomize: true,
16 | },
17 | },
18 | dirs: ["src/jobs"],
19 | });
20 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ESNext",
4 | "moduleResolution": "Bundler",
5 | "allowJs": true,
6 | "jsx": "preserve",
7 | "noEmit": true,
8 | "target": "ES2017",
9 | "lib": ["dom", "dom.iterable", "esnext"],
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "esModuleInterop": true,
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "baseUrl": ".",
22 | "paths": {
23 | "@/*": ["src/*"]
24 | },
25 | },
26 | "include": [
27 | "next-env.d.ts",
28 | "**/*.ts",
29 | "**/*.tsx",
30 | ".next/types/**/*.ts",
31 | "trigger.config.ts"
32 | ],
33 | "exclude": ["node_modules", ".next"]
34 | }
35 |
--------------------------------------------------------------------------------
/apps/web/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "regions": ["fra1", "syd1", "sfo1"]
3 | }
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3 | "organizeImports": {
4 | "enabled": true
5 | },
6 | "linter": {
7 | "ignore": ["node_modules", ".next", "*.json", "test", "dist"],
8 | "enabled": true,
9 | "rules": {
10 | "recommended": true,
11 | "a11y": {
12 | "noSvgWithoutTitle": "off",
13 | "useKeyWithClickEvents": "off"
14 | },
15 | "style": {
16 | "noNonNullAssertion": "off"
17 | },
18 | "correctness": {
19 | "useExhaustiveDependencies": "off"
20 | }
21 | }
22 | },
23 | "formatter": {
24 | "indentStyle": "space",
25 | "ignore": ["*.json", ".next", "dist", "test"]
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/android/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["es"]
5 | },
6 | "files": {
7 | "android": {
8 | "include": ["locales/[locale].xml"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/bun.lockb
--------------------------------------------------------------------------------
/examples/complex-keys/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["de"]
5 | },
6 | "files": {
7 | "json": {
8 | "include": ["messages/[locale].json"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/complex-keys/languine.lock:
--------------------------------------------------------------------------------
1 | version: 1
2 | files:
3 | messages/en.json:
4 | chat.poll.allow-multiple: b12390f674492adc8dadac6b407e040d
5 | chat.poll.create-poll: 87dde47c3c24ba7cc629ea2dacab3806
6 | chat.poll.create-poll\.title: 6cb41bdaa77f037dfa8ec7db1bd346cd
7 | chat.poll.create-poll\.question: a97ea56b0e00b2379736ae60869ff66a
8 | chat.poll.create-poll\.options: dae8ace18bdcbcc6ae5aece263e14fe8
9 | chat.poll.create-poll\.placeholder\.add: ec211f7c20af43e742bf2570c3cb84f9
10 | test.*: 73f0a5236a013c8ee6af7d96fee67df5
11 | test.image/*, \.jpg, \.jpeg, \.png, \.gif, \.svg, \.webp: fff0d600f8a0b5e19e88bfb821dd1157
12 | test.\.mp4, \.mov, \.avi, \.mkv, \.webm, \.mpeg: 554cfab3938e21d9270bd6b75931f96f
13 | test.\.mp3, \.wav, \.ogg, \.flac, \.aac, \.wma, \.m4a, \.midi, \.alac: b22f0418e8ac915eb66f829d262d14a2
14 | test.\.pdf: abdf095626d4a4ab2bcd4167b128c1f1
15 | test.\.docx: 8c7a7a618a6d4acfe85b4b4b1048846d
16 | test.\.csv: 01b1b6ebeb388697cca6712b58439069
17 | test.\.pptx: a9bec49e3e6504112d702f6449b79631
18 | test.\.xlsx: 552c070e14cf0a9151d1b49064e4cde0
19 | test.\.zip: daf4de58883c030f033f07e61debf025
20 |
--------------------------------------------------------------------------------
/examples/complex-keys/messages/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "chat": {
3 | "poll": {
4 | "allow-multiple": "Mehrere Antworten zulassen?",
5 | "create-poll": "Umfrage",
6 | "create-poll.title": "Umfrage erstellen",
7 | "create-poll.question": "Frage",
8 | "create-poll.options": "Optionen",
9 | "create-poll.placeholder.add": "Hinzufügen"
10 | }
11 | },
12 | "test": {
13 | "*": "Alle Dateitypen zulassen",
14 | "image/*, .jpg, .jpeg, .png, .gif, .svg, .webp": "Bilder",
15 | ".mp4, .mov, .avi, .mkv, .webm, .mpeg": "Videos",
16 | ".mp3, .wav, .ogg, .flac, .aac, .wma, .m4a, .midi, .alac": "Audio",
17 | ".pdf": ".pdf",
18 | ".docx": ".docx",
19 | ".csv": ".csv",
20 | ".pptx": ".pptx",
21 | ".xlsx": ".xlsx",
22 | ".zip": ".zip"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/complex-keys/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "chat": {
3 | "poll": {
4 | "allow-multiple": "Allow Multiple Answers?",
5 | "create-poll": "Poll",
6 | "create-poll.title": "Create Poll",
7 | "create-poll.question": "Question",
8 | "create-poll.options": "Options",
9 | "create-poll.placeholder.add": "Add"
10 | }
11 | },
12 | "test": {
13 | "*": "Allow all file types",
14 | "image/*, .jpg, .jpeg, .png, .gif, .svg, .webp": "Images",
15 | ".mp4, .mov, .avi, .mkv, .webm, .mpeg": "Videos",
16 | ".mp3, .wav, .ogg, .flac, .aac, .wma, .m4a, .midi, .alac": "Audio",
17 | ".pdf": ".pdf",
18 | ".docx": ".docx",
19 | ".csv": ".csv",
20 | ".pptx": ".pptx",
21 | ".xlsx": ".xlsx",
22 | ".zip": ".zip"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/expo/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 |
6 | # Expo
7 | .expo/
8 | dist/
9 | web-build/
10 | expo-env.d.ts
11 |
12 | # Native
13 | *.orig.*
14 | *.jks
15 | *.p8
16 | *.p12
17 | *.key
18 | *.mobileprovision
19 |
20 | # Metro
21 | .metro-health-check*
22 |
23 | # debug
24 | npm-debug.*
25 | yarn-debug.*
26 | yarn-error.*
27 |
28 | # macOS
29 | .DS_Store
30 | *.pem
31 |
32 | # local env files
33 | .env*.local
34 |
35 | # typescript
36 | *.tsbuildinfo
37 |
38 | app-example
39 |
--------------------------------------------------------------------------------
/examples/expo/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # my-app
2 |
3 | ## 1.0.2
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies [[`b21abd6`](https://github.com/midday-ai/languine/commit/b21abd6481288240ee63125f81fb122c4da4c2fc)]:
8 | - languine@1.0.0
9 |
10 | ## 1.0.1
11 |
12 | ### Patch Changes
13 |
14 | - Updated dependencies [[`58ffd57`](https://github.com/midday-ai/languine/commit/58ffd574f5de0bea09702fd20959fe556a644e81), [`c62d4c0`](https://github.com/midday-ai/languine/commit/c62d4c00d447929b023f571927b429d04fa0e0fd)]:
15 | - languine@1.0.0
16 |
--------------------------------------------------------------------------------
/examples/expo/app/+not-found.tsx:
--------------------------------------------------------------------------------
1 | import { Link, Stack } from "expo-router";
2 | import { StyleSheet } from "react-native";
3 |
4 | import { ThemedText } from "@/components/ThemedText";
5 | import { ThemedView } from "@/components/ThemedView";
6 |
7 | export default function NotFoundScreen() {
8 | return (
9 | <>
10 |
11 |
12 | This screen doesn't exist.
13 |
14 | Go to home screen!
15 |
16 |
17 | >
18 | );
19 | }
20 |
21 | const styles = StyleSheet.create({
22 | container: {
23 | flex: 1,
24 | alignItems: "center",
25 | justifyContent: "center",
26 | padding: 20,
27 | },
28 | link: {
29 | marginTop: 15,
30 | paddingVertical: 15,
31 | },
32 | });
33 |
--------------------------------------------------------------------------------
/examples/expo/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/expo/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/examples/expo/assets/images/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/expo/assets/images/adaptive-icon.png
--------------------------------------------------------------------------------
/examples/expo/assets/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/expo/assets/images/favicon.png
--------------------------------------------------------------------------------
/examples/expo/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/expo/assets/images/icon.png
--------------------------------------------------------------------------------
/examples/expo/assets/images/partial-react-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/expo/assets/images/partial-react-logo.png
--------------------------------------------------------------------------------
/examples/expo/assets/images/react-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/expo/assets/images/react-logo.png
--------------------------------------------------------------------------------
/examples/expo/assets/images/react-logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/expo/assets/images/react-logo@2x.png
--------------------------------------------------------------------------------
/examples/expo/assets/images/react-logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/expo/assets/images/react-logo@3x.png
--------------------------------------------------------------------------------
/examples/expo/assets/images/splash-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/expo/assets/images/splash-icon.png
--------------------------------------------------------------------------------
/examples/expo/components/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from "expo-router";
2 | import { openBrowserAsync } from "expo-web-browser";
3 | import { type ComponentProps } from "react";
4 | import { Platform } from "react-native";
5 |
6 | type Props = Omit, "href"> & { href: string };
7 |
8 | export function ExternalLink({ href, ...rest }: Props) {
9 | return (
10 | {
15 | if (Platform.OS !== "web") {
16 | // Prevent the default behavior of linking to the default browser on native.
17 | event.preventDefault();
18 | // Open the link in an in-app browser.
19 | await openBrowserAsync(href);
20 | }
21 | }}
22 | />
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/examples/expo/components/HapticTab.tsx:
--------------------------------------------------------------------------------
1 | import { BottomTabBarButtonProps } from "@react-navigation/bottom-tabs";
2 | import { PlatformPressable } from "@react-navigation/elements";
3 | import * as Haptics from "expo-haptics";
4 |
5 | export function HapticTab(props: BottomTabBarButtonProps) {
6 | return (
7 | {
10 | if (process.env.EXPO_OS === "ios") {
11 | // Add a soft haptic feedback when pressing down on the tabs.
12 | Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
13 | }
14 | props.onPressIn?.(ev);
15 | }}
16 | />
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/expo/components/HelloWave.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { StyleSheet } from "react-native";
3 | import Animated, {
4 | useSharedValue,
5 | useAnimatedStyle,
6 | withTiming,
7 | withRepeat,
8 | withSequence,
9 | } from "react-native-reanimated";
10 |
11 | import { ThemedText } from "@/components/ThemedText";
12 |
13 | export function HelloWave() {
14 | const rotationAnimation = useSharedValue(0);
15 |
16 | useEffect(() => {
17 | rotationAnimation.value = withRepeat(
18 | withSequence(
19 | withTiming(25, { duration: 150 }),
20 | withTiming(0, { duration: 150 }),
21 | ),
22 | 4, // Run the animation 4 times
23 | );
24 | }, []);
25 |
26 | const animatedStyle = useAnimatedStyle(() => ({
27 | transform: [{ rotate: `${rotationAnimation.value}deg` }],
28 | }));
29 |
30 | return (
31 |
32 | 👋
33 |
34 | );
35 | }
36 |
37 | const styles = StyleSheet.create({
38 | text: {
39 | fontSize: 28,
40 | lineHeight: 32,
41 | marginTop: -6,
42 | },
43 | });
44 |
--------------------------------------------------------------------------------
/examples/expo/components/ThemedView.tsx:
--------------------------------------------------------------------------------
1 | import { View, type ViewProps } from "react-native";
2 |
3 | import { useThemeColor } from "@/hooks/useThemeColor";
4 |
5 | export type ThemedViewProps = ViewProps & {
6 | lightColor?: string;
7 | darkColor?: string;
8 | };
9 |
10 | export function ThemedView({
11 | style,
12 | lightColor,
13 | darkColor,
14 | ...otherProps
15 | }: ThemedViewProps) {
16 | const backgroundColor = useThemeColor(
17 | { light: lightColor, dark: darkColor },
18 | "background",
19 | );
20 |
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/examples/expo/components/__tests__/ThemedText-test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import renderer from "react-test-renderer";
3 |
4 | import { ThemedText } from "../ThemedText";
5 |
6 | it(`renders correctly`, () => {
7 | const tree = renderer
8 | .create(Snapshot test!)
9 | .toJSON();
10 |
11 | expect(tree).toMatchSnapshot();
12 | });
13 |
--------------------------------------------------------------------------------
/examples/expo/components/__tests__/__snapshots__/ThemedText-test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = `
4 |
22 | Snapshot test!
23 |
24 | `;
25 |
--------------------------------------------------------------------------------
/examples/expo/components/ui/IconSymbol.ios.tsx:
--------------------------------------------------------------------------------
1 | import { SymbolView, SymbolViewProps, SymbolWeight } from "expo-symbols";
2 | import { StyleProp, ViewStyle } from "react-native";
3 |
4 | export function IconSymbol({
5 | name,
6 | size = 24,
7 | color,
8 | style,
9 | weight = "regular",
10 | }: {
11 | name: SymbolViewProps["name"];
12 | size?: number;
13 | color: string;
14 | style?: StyleProp;
15 | weight?: SymbolWeight;
16 | }) {
17 | return (
18 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/examples/expo/components/ui/TabBarBackground.ios.tsx:
--------------------------------------------------------------------------------
1 | import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
2 | import { BlurView } from "expo-blur";
3 | import { StyleSheet } from "react-native";
4 | import { useSafeAreaInsets } from "react-native-safe-area-context";
5 |
6 | export default function BlurTabBarBackground() {
7 | return (
8 |
15 | );
16 | }
17 |
18 | export function useBottomTabOverflow() {
19 | const tabHeight = useBottomTabBarHeight();
20 | const { bottom } = useSafeAreaInsets();
21 | return tabHeight - bottom;
22 | }
23 |
--------------------------------------------------------------------------------
/examples/expo/components/ui/TabBarBackground.tsx:
--------------------------------------------------------------------------------
1 | // This is a shim for web and Android where the tab bar is generally opaque.
2 | export default undefined;
3 |
4 | export function useBottomTabOverflow() {
5 | return 0;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/expo/constants/Colors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Below are the colors that are used in the app. The colors are defined in the light and dark mode.
3 | * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
4 | */
5 |
6 | const tintColorLight = "#0a7ea4";
7 | const tintColorDark = "#fff";
8 |
9 | export const Colors = {
10 | light: {
11 | text: "#11181C",
12 | background: "#fff",
13 | tint: tintColorLight,
14 | icon: "#687076",
15 | tabIconDefault: "#687076",
16 | tabIconSelected: tintColorLight,
17 | },
18 | dark: {
19 | text: "#ECEDEE",
20 | background: "#151718",
21 | tint: tintColorDark,
22 | icon: "#9BA1A6",
23 | tabIconDefault: "#9BA1A6",
24 | tabIconSelected: tintColorDark,
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/examples/expo/hooks/useColorScheme.ts:
--------------------------------------------------------------------------------
1 | export { useColorScheme } from "react-native";
2 |
--------------------------------------------------------------------------------
/examples/expo/hooks/useColorScheme.web.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useColorScheme as useRNColorScheme } from "react-native";
3 |
4 | /**
5 | * To support static rendering, this value needs to be re-calculated on the client side for web
6 | */
7 | export function useColorScheme() {
8 | const [hasHydrated, setHasHydrated] = useState(false);
9 |
10 | useEffect(() => {
11 | setHasHydrated(true);
12 | }, []);
13 |
14 | const colorScheme = useRNColorScheme();
15 |
16 | if (hasHydrated) {
17 | return colorScheme;
18 | }
19 |
20 | return "light";
21 | }
22 |
--------------------------------------------------------------------------------
/examples/expo/hooks/useThemeColor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Learn more about light and dark modes:
3 | * https://docs.expo.dev/guides/color-schemes/
4 | */
5 |
6 | import { Colors } from "@/constants/Colors";
7 | import { useColorScheme } from "@/hooks/useColorScheme";
8 |
9 | export function useThemeColor(
10 | props: { light?: string; dark?: string },
11 | colorName: keyof typeof Colors.light & keyof typeof Colors.dark,
12 | ) {
13 | const theme = useColorScheme() ?? "light";
14 | const colorFromProps = props[theme];
15 |
16 | if (colorFromProps) {
17 | return colorFromProps;
18 | } else {
19 | return Colors[theme][colorName];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/expo/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectId": "",
3 | "locale": {
4 | "source": "en",
5 | "targets": [
6 | "es",
7 | "sv"
8 | ]
9 | },
10 | "files": {
11 | "json": {
12 | "include": [
13 | "locales/native/[locale].json",
14 | "locales/[locale].json"
15 | ]
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/examples/expo/languine.lock:
--------------------------------------------------------------------------------
1 | version: 1
2 | files:
3 | locales/native/en.json:
4 | CFBundleDisplayName: e4cbf9f83134f35596e3d6d858d66cfe
5 | NSContactsUsageDescription: 2f8bdc22595adee5ae450d38d88ac140
6 | locales/en.json:
7 | welcome: 0cc23d873da8dd04c163ab6f8a25755c
8 | hello: 8b1a9953c4611296a827abf8c47804d7
9 | settings: f4f70727dc34561dfde1a3c529b6205c
10 |
--------------------------------------------------------------------------------
/examples/expo/locales/README.md:
--------------------------------------------------------------------------------
1 | # Localization Setup
2 |
3 | This project uses Expo Localization for handling multiple languages.
4 |
5 | ## Structure
6 |
7 | - `locales/i18n.ts` - Main i18n configuration
8 | - `locales/{lang}.json` - Translation files for each language
9 | - `locales/native/{lang}.json` - Native app metadata translations
10 |
11 | ## Usage
12 |
13 | Import the i18n instance in your components:
14 |
15 | ```tsx
16 | import i18n from './locales/i18n';
17 |
18 | function MyComponent() {
19 | return {i18n.t('welcome')};
20 | }
21 | ```
22 |
23 | ## Adding New Translations
24 |
25 | 1. Add translations to each language file
26 | 2. Run `languine translate` to start translating
--------------------------------------------------------------------------------
/examples/expo/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": "Welcome to my app",
3 | "hello": "Hello",
4 | "settings": "Settings"
5 | }
--------------------------------------------------------------------------------
/examples/expo/locales/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": "Bienvenido a mi aplicación",
3 | "hello": "Hola",
4 | "settings": "Configuraciones"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/expo/locales/i18n.ts:
--------------------------------------------------------------------------------
1 | // For more information on Expo Localization and usage: https://docs.expo.dev/guides/localization
2 | import { getLocales } from 'expo-localization';
3 | import { I18n } from 'i18n-js';
4 |
5 | const translations = {
6 | en: require('./en.json'),
7 | es: require('./es.json'),
8 | sv: require('./sv.json')
9 | }
10 |
11 | const i18n = new I18n(translations);
12 |
13 | // Set the locale once at the beginning of your app
14 | i18n.locale = getLocales().at(0)?.languageCode ?? 'en';
15 |
16 | // When a value is missing from a language it'll fallback to another language with the key present
17 | i18n.enableFallback = true;
18 |
19 | export default i18n;
--------------------------------------------------------------------------------
/examples/expo/locales/native/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "CFBundleDisplayName": "My App",
3 | "NSContactsUsageDescription": "We need access to contacts to help you connect with friends"
4 | }
--------------------------------------------------------------------------------
/examples/expo/locales/native/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "CFBundleDisplayName": "Mi Aplicación",
3 | "NSContactsUsageDescription": "Necesitamos acceso a los contactos para ayudarte a conectar con amigos"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/expo/locales/native/sv.json:
--------------------------------------------------------------------------------
1 | {
2 | "CFBundleDisplayName": "Min App",
3 | "NSContactsUsageDescription": "Vi behöver åtkomst till kontakter för att hjälpa dig att koppla ihop med vänner"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/expo/locales/sv.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": "Välkommen till min app",
3 | "hello": "Hej",
4 | "settings": "Inställningar"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/expo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true,
5 | "paths": {
6 | "@/*": ["./*"]
7 | }
8 | },
9 | "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/examples/ftl/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectId": "prj_bh3yk1gbw6jcft83o2b517wp",
3 | "locale": {
4 | "source": "en",
5 | "targets": [
6 | "sv"
7 | ]
8 | },
9 | "files": {
10 | "ftl": {
11 | "include": [
12 | "locales/[locale].ftl"
13 | ]
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/examples/fumadocs/.gitignore:
--------------------------------------------------------------------------------
1 | # deps
2 | /node_modules
3 |
4 | # generated content
5 | .contentlayer
6 | .content-collections
7 | .source
8 |
9 | # test & build
10 | /coverage
11 | /.next/
12 | /out/
13 | /build
14 | *.tsbuildinfo
15 |
16 | # misc
17 | .DS_Store
18 | *.pem
19 | /.pnp
20 | .pnp.js
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # others
26 | .env*.local
27 | .vercel
28 | next-env.d.ts
--------------------------------------------------------------------------------
/examples/fumadocs/app/api/search/route.ts:
--------------------------------------------------------------------------------
1 | import { source } from "@/lib/source";
2 | import { createFromSource } from "fumadocs-core/search/server";
3 |
4 | export const { GET } = createFromSource(source);
5 |
--------------------------------------------------------------------------------
/examples/fumadocs/app/layout.config.tsx:
--------------------------------------------------------------------------------
1 | import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
2 |
3 | const translations = {
4 | en: (await import("@/content/app.json")).default,
5 | cn: (await import("@/content/app.cn.json")).default,
6 | } as const;
7 |
8 | export const baseOptions = (locale: "en" | "cn") => {
9 | return {
10 | nav: {
11 | title: translations[locale].title,
12 | },
13 | i18n: true,
14 | } satisfies BaseLayoutProps;
15 | };
16 |
--------------------------------------------------------------------------------
/examples/fumadocs/content/docs/cn/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 你好,世界
3 | description: 你的第一份文档
4 | ---
5 |
6 | 欢迎来到文档!你可以在 `/content/docs` 开始编写文档。
7 |
8 | ## 接下来是什么?
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/fumadocs/content/docs/cn/test.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 组件
3 | description: 组件
4 | ---
5 |
6 | ## 代码块
7 |
8 | ```js
9 | console.log('Hello World');
10 | ```
11 |
12 | ## 卡片
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/fumadocs/content/docs/en/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Hello World
3 | description: Your first document
4 | ---
5 |
6 | Welcome to the docs! You can start writing documents in `/content/docs`.
7 |
8 | ## What is Next?
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/fumadocs/content/docs/en/test.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Components
3 | description: Components
4 | ---
5 |
6 | ## Code Block
7 |
8 | ```js
9 | console.log('Hello World');
10 | ```
11 |
12 | ## Cards
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/fumadocs/content/ui.cn.json:
--------------------------------------------------------------------------------
1 | {
2 | "search": "搜索",
3 | "searchNoResult": "未找到结果",
4 | "toc": "在本页",
5 | "tocNoHeadings": "无标题",
6 | "lastUpdate": "最后更新于",
7 | "chooseLanguage": "选择语言",
8 | "nextPage": "下一页",
9 | "previousPage": "上一页",
10 | "chooseTheme": "主题",
11 | "editOnGithub": "在 GitHub 上编辑"
12 | }
13 |
--------------------------------------------------------------------------------
/examples/fumadocs/content/ui.en.json:
--------------------------------------------------------------------------------
1 | {
2 | "search": "Search",
3 | "searchNoResult": "No results found",
4 | "toc": "On this page",
5 | "tocNoHeadings": "No Headings",
6 | "lastUpdate": "Last updated on",
7 | "chooseLanguage": "Choose a language",
8 | "nextPage": "Next",
9 | "previousPage": "Previous",
10 | "chooseTheme": "Theme",
11 | "editOnGithub": "Edit on GitHub"
12 | }
--------------------------------------------------------------------------------
/examples/fumadocs/content/ui.json:
--------------------------------------------------------------------------------
1 | {
2 | "search": "Search",
3 | "searchNoResult": "No results found",
4 | "toc": "On this page",
5 | "tocNoHeadings": "No Headings",
6 | "lastUpdate": "Last updated on",
7 | "chooseLanguage": "Choose a language",
8 | "nextPage": "Next",
9 | "previousPage": "Previous",
10 | "chooseTheme": "Theme",
11 | "editOnGithub": "Edit on GitHub"
12 | }
--------------------------------------------------------------------------------
/examples/fumadocs/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["fr", "es"]
5 | },
6 | "files": {
7 | "json": {
8 | "include": ["content/ui.[locale].json"]
9 | },
10 | "md": {
11 | "include": ["content/docs/[locale]/**/*.mdx"]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/fumadocs/lib/i18n.ts:
--------------------------------------------------------------------------------
1 | import type { I18nConfig } from "fumadocs-core/i18n";
2 |
3 | export const i18n: I18nConfig = {
4 | defaultLanguage: "en",
5 | languages: ["en", "cn"],
6 | };
7 |
--------------------------------------------------------------------------------
/examples/fumadocs/lib/source.ts:
--------------------------------------------------------------------------------
1 | import { docs, meta } from "@/.source";
2 | import { createMDXSource } from "fumadocs-mdx";
3 | import { loader } from "fumadocs-core/source";
4 | import { i18n } from "./i18n";
5 |
6 | export const source = loader({
7 | baseUrl: "/",
8 | source: createMDXSource(docs, meta),
9 | i18n: i18n,
10 | });
11 |
--------------------------------------------------------------------------------
/examples/fumadocs/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createI18nMiddleware } from "fumadocs-core/i18n";
2 | import { i18n } from "./lib/i18n";
3 |
4 | export default createI18nMiddleware(i18n);
5 |
6 | export const config = {
7 | // Matcher ignoring `/_next/` and `/api/`
8 | matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
9 | };
10 |
--------------------------------------------------------------------------------
/examples/fumadocs/next.config.mjs:
--------------------------------------------------------------------------------
1 | import { createMDX } from "fumadocs-mdx/next";
2 |
3 | const withMDX = createMDX();
4 |
5 | /** @type {import('next').NextConfig} */
6 | const config = {
7 | reactStrictMode: true,
8 | };
9 |
10 | export default withMDX(config);
11 |
--------------------------------------------------------------------------------
/examples/fumadocs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fumadocs",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "next build",
7 | "dev": "next dev",
8 | "start": "next start",
9 | "postinstall": "fumadocs-mdx"
10 | },
11 | "dependencies": {
12 | "fast-glob": "^3.3.2",
13 | "fumadocs-core": "14.6.8",
14 | "fumadocs-mdx": "11.2.1",
15 | "fumadocs-ui": "14.6.8",
16 | "next": "15.1.2",
17 | "react": "^19.0.0",
18 | "react-dom": "^19.0.0"
19 | },
20 | "devDependencies": {
21 | "@types/node": "22.10.2",
22 | "@types/react": "^19.0.2",
23 | "@types/react-dom": "^19.0.2",
24 | "typescript": "^5.7.2",
25 | "@types/mdx": "^2.0.13"
26 | }
27 | }
--------------------------------------------------------------------------------
/examples/fumadocs/source.config.ts:
--------------------------------------------------------------------------------
1 | import { defineDocs, defineConfig } from "fumadocs-mdx/config";
2 |
3 | export const { docs, meta } = defineDocs({
4 | dir: "content/docs",
5 | });
6 |
7 | export default defineConfig();
8 |
--------------------------------------------------------------------------------
/examples/fumadocs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "target": "ESNext",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "strict": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "noEmit": true,
11 | "esModuleInterop": true,
12 | "module": "esnext",
13 | "moduleResolution": "bundler",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "jsx": "preserve",
17 | "incremental": true,
18 | "paths": {
19 | "@/*": ["./*"]
20 | },
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
28 | "exclude": ["node_modules"]
29 | }
30 |
--------------------------------------------------------------------------------
/examples/i18next/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["es", "fr"]
5 | },
6 | "files": {
7 | "json": {
8 | "include": ["locales/[locale].json"]
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/examples/i18next/languine.lock:
--------------------------------------------------------------------------------
1 | version: 1
2 | files:
3 | locales/en.json:
4 | key: e381eae446dcf987894b521acac9d8b0
5 | nested.key: f25216919cc1d32c92cae6894167c2a3
6 | interpolated: 248ba0c68233725258e8d012af024b4d
7 | pluralKey_one: 7657a288705c324198437b5da7514764
8 | pluralKey_other: e57159fea2e7a665ee8156241a03366e
9 |
--------------------------------------------------------------------------------
/examples/i18next/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "key": "This is a new value",
3 | "nested": {
4 | "key": "Another new value"
5 | },
6 | "interpolated": "New value with {{name}}!",
7 | "pluralKey_one": "One new value",
8 | "pluralKey_other": "Multiple new values"
9 | }
10 |
--------------------------------------------------------------------------------
/examples/i18next/locales/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "key": "Este es un nuevo valor",
3 | "nested": {
4 | "key": "Otro nuevo valor"
5 | },
6 | "interpolated": "¡Nuevo valor con {{name}}!",
7 | "pluralKey_one": "Un nuevo valor",
8 | "pluralKey_other": "Varios nuevos valores"
9 | }
10 |
--------------------------------------------------------------------------------
/examples/i18next/locales/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "key": "Ceci est une nouvelle valeur",
3 | "nested": {
4 | "key": "Une autre nouvelle valeur"
5 | },
6 | "interpolated": "Nouvelle valeur avec {{name}} !",
7 | "pluralKey_one": "Une nouvelle valeur",
8 | "pluralKey_other": "Plusieurs nouvelles valeurs"
9 | }
10 |
--------------------------------------------------------------------------------
/examples/lingui/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["de"]
5 | },
6 | "files": {
7 | "po": {
8 | "include": ["locales/[locale]/messages.po"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/lingui/locales/de/messages.po:
--------------------------------------------------------------------------------
1 | msgid "{selected, plural, one {Developer} other {Developers}}"
2 | msgstr "{selected, plural, one {Entwickler} other {Entwickler}}"
3 |
4 | msgid "English"
5 | msgstr "Englisch"
6 |
7 | msgid "next-explanation"
8 | msgstr "Next.js ist ein Open-Source-React-Frontend-Entwicklungs-Framework, das Funktionen wie serverseitiges Rendering und die Generierung statischer Websites für React-basierte Webanwendungen ermöglicht. Es ist ein produktionsreifes Framework, das Entwicklern ermöglicht, schnell statische und dynamische JAMstack-Websites zu erstellen, und wird von vielen großen Unternehmen weit verbreitet eingesetzt."
9 |
10 | msgid "Plural Test: How many developers?"
11 | msgstr "Plural-Test: Wie viele Entwickler?"
12 |
13 | msgid "Serbian"
14 | msgstr "Serbisch"
15 |
16 | msgid "Spanish"
17 | msgstr "Spanisch"
18 |
19 | msgid "Translation Demo"
20 | msgstr "Übersetzungsdemo"
21 |
--------------------------------------------------------------------------------
/examples/markdown/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectId": "",
3 | "locale": {
4 | "source": "en",
5 | "targets": ["sv"]
6 | },
7 | "files": {
8 | "mdx": {
9 | "include": ["blog/[locale]/*.mdx"]
10 | },
11 | "md": {
12 | "include": ["blog/[locale]/*.md"]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/markdown/languine.lock:
--------------------------------------------------------------------------------
1 | version: 1
2 | files:
3 | blog/en/assistant.mdx:
4 | content: 4b43bed1cf80484e340ac311184ab41b
5 | blog/en/dub.md:
6 | content: 5faca1ebb0a8fdf1d55ed1f07f46fc7c
7 |
--------------------------------------------------------------------------------
/examples/monorepo/apps/web/src/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "test": "This is a great"
3 | }
--------------------------------------------------------------------------------
/examples/multiple/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": [
5 | "sv",
6 | "en"
7 | ]
8 | },
9 | "files": {
10 | "json": {
11 | "include": [
12 | "src/locales/[locale]/*.json"
13 | ]
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/examples/multiple/languine.lock:
--------------------------------------------------------------------------------
1 | version: 1
2 | files:
3 | src/locales/en/hero.json:
4 | hero.title: eaa915e5e764b18f0c68e0b8c5edae1f
5 | hero.description: 9a6903167c131aa085d3eb1e5b06ff9b
6 | hero.cta: f88b12c6e29f09d28dc74104cf8eb0bd
7 | src/locales/en/menu.json:
8 | menu.home: 43eea8e8ea25503a477a5443ff8292af
9 | menu.about: 8f7f4c1ce7a4f933663d10543562b096
10 | menu.contact: bbaff12800505b22a853e8b7f4eb6a22
11 | menu.login: 99dea78007133396a7b8ed70578ac6ae
12 | menu.register: 0ba7583639a274c434bbe6ef797115a4
13 |
--------------------------------------------------------------------------------
/examples/multiple/src/locales/en/hero.json:
--------------------------------------------------------------------------------
1 | {
2 | "hero": {
3 | "title": "Hero Title",
4 | "description": "Hero Description",
5 | "cta": "Hero CTA"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/multiple/src/locales/en/menu.json:
--------------------------------------------------------------------------------
1 | {
2 | "menu": {
3 | "home": "Home2",
4 | "about": "About",
5 | "contact": "Contact",
6 | "login": "Login",
7 | "register": "Register"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/multiple/src/locales/sv/hero.json:
--------------------------------------------------------------------------------
1 | {
2 | "hero": {
3 | "title": "Hjälte Titel2",
4 | "description": "Hjälte Beskrivning",
5 | "cta": "Hjälte CTA"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/multiple/src/locales/sv/menu.json:
--------------------------------------------------------------------------------
1 | {
2 | "menu": {
3 | "about": "Om2",
4 | "contact": "Kontakt2",
5 | "login": "Logga in2",
6 | "register": "Registrera2",
7 | "home": "Hem2"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/namespace/docs/de/welcome.md:
--------------------------------------------------------------------------------
1 | ## Willkommen bei Languine
2 |
3 | Dies ist eine Willkommensnachricht.
4 |
5 | ## Erste Schritte
6 |
7 | Um mit unserer Anwendung zu beginnen:
8 |
9 | 1. Installieren Sie die Abhängigkeiten mit `npm install`
10 | 2. Konfigurieren Sie Ihre Umgebungsvariablen in `.env`
11 | 3. Starten Sie den Entwicklungsserver mit `npm run dev`
12 |
13 | ## Funktionen
14 |
15 | Unsere Anwendung umfasst:
16 |
17 | - Echtzeit-Datensynchronisation
18 | - Mehrsprachige Unterstützung
19 | - Responsives Design
20 | - Dunkle/Helle Themenmodi
21 |
22 | ## Dokumentation
23 |
24 | Weitere detaillierte Informationen finden Sie unter:
25 |
26 | - [Installationsanleitung](./installation.md)
27 | - [API-Referenz](./api-reference.md)
28 | - [Richtlinien zur Mitwirkung](./contributing.md)
29 |
30 | ## Unterstützung
31 |
32 | Brauchen Sie Hilfe? Sie können:
33 |
34 | - Unserer [Discord-Community](https://discord.gg/example) beitreten
35 | - Ein Problem auf [GitHub](https://github.com/example/repo) öffnen
36 | - Uns eine E-Mail an support@example.com senden
37 |
38 | ## Lizenz
39 |
40 | Dieses Projekt steht unter der MIT-Lizenz - siehe die [LICENSE](./LICENSE) Datei für Details.
--------------------------------------------------------------------------------
/examples/namespace/docs/en/welcome.md:
--------------------------------------------------------------------------------
1 | ## Welcome to Languine
2 |
3 | This is a welcome message.
4 |
5 | ## Getting Started
6 |
7 | To get started with our application:
8 |
9 | 1. Install the dependencies by running `npm install`
10 | 2. Configure your environment variables in `.env`
11 | 3. Start the development server with `npm run dev`
12 |
13 | ## Features
14 |
15 | Our application includes:
16 |
17 | - Real-time data synchronization
18 | - Multi-language support
19 | - Responsive design
20 | - Dark/Light theme modes
21 |
22 | ## Documentation
23 |
24 | For more detailed information, check out:
25 |
26 | - [Installation Guide](./installation.md)
27 | - [API Reference](./api-reference.md)
28 | - [Contributing Guidelines](./contributing.md)
29 |
30 | ## Support
31 |
32 | Need help? You can:
33 |
34 | - Join our [Discord community](https://discord.gg/example)
35 | - Open an issue on [GitHub](https://github.com/example/repo)
36 | - Email us at support@example.com
37 |
38 | ## License
39 |
40 | This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
--------------------------------------------------------------------------------
/examples/namespace/docs/es/welcome.md:
--------------------------------------------------------------------------------
1 | ## Bienvenido a Languine
2 |
3 | Este es un mensaje de bienvenida.
4 |
5 | ## Comenzando
6 |
7 | Para comenzar con nuestra aplicación:
8 |
9 | 1. Instala las dependencias ejecutando `npm install`
10 | 2. Configura tus variables de entorno en `.env`
11 | 3. Inicia el servidor de desarrollo con `npm run dev`
12 |
13 | ## Características
14 |
15 | Nuestra aplicación incluye:
16 |
17 | - Sincronización de datos en tiempo real
18 | - Soporte para múltiples idiomas
19 | - Diseño responsivo
20 | - Modos de tema oscuro/claro
21 |
22 | ## Documentación
23 |
24 | Para obtener información más detallada, consulta:
25 |
26 | - [Guía de Instalación](./installation.md)
27 | - [Referencia de la API](./api-reference.md)
28 | - [Pautas de Contribución](./contributing.md)
29 |
30 | ## Soporte
31 |
32 | ¿Necesitas ayuda? Puedes:
33 |
34 | - Unirte a nuestra [comunidad de Discord](https://discord.gg/example)
35 | - Abrir un problema en [GitHub](https://github.com/example/repo)
36 | - Enviarnos un correo electrónico a support@example.com
37 |
38 | ## Licencia
39 |
40 | Este proyecto está licenciado bajo la Licencia MIT - consulta el archivo [LICENCIA](./LICENSE) para más detalles.
--------------------------------------------------------------------------------
/examples/namespace/docs/fr/welcome.md:
--------------------------------------------------------------------------------
1 | ## Bienvenue sur Languine
2 |
3 | Ceci est un message de bienvenue.
4 |
5 | ## Pour commencer
6 |
7 | Pour commencer avec notre application :
8 |
9 | 1. Installez les dépendances en exécutant `npm install`
10 | 2. Configurez vos variables d'environnement dans `.env`
11 | 3. Démarrez le serveur de développement avec `npm run dev`
12 |
13 | ## Fonctionnalités
14 |
15 | Notre application comprend :
16 |
17 | - Synchronisation des données en temps réel
18 | - Support multilingue
19 | - Conception réactive
20 | - Modes thème sombre/clair
21 |
22 | ## Documentation
23 |
24 | Pour plus d'informations détaillées, consultez :
25 |
26 | - [Guide d'installation](./installation.md)
27 | - [Référence API](./api-reference.md)
28 | - [Lignes directrices pour contribuer](./contributing.md)
29 |
30 | ## Support
31 |
32 | Besoin d'aide ? Vous pouvez :
33 |
34 | - Rejoindre notre [communauté Discord](https://discord.gg/example)
35 | - Ouvrir un problème sur [GitHub](https://github.com/example/repo)
36 | - Nous envoyer un e-mail à support@example.com
37 |
38 | ## Licence
39 |
40 | Ce projet est sous licence MIT - voir le fichier [LICENCE](./LICENSE) pour plus de détails.
--------------------------------------------------------------------------------
/examples/namespace/docs/sv/welcome.md:
--------------------------------------------------------------------------------
1 | ## Välkommen till Languine
2 |
3 | Detta är ett välkomstmeddelande.
4 |
5 | ## Komma igång
6 |
7 | För att komma igång med vår applikation:
8 |
9 | 1. Installera beroendena genom att köra `npm install`
10 | 2. Konfigurera dina miljövariabler i `.env`
11 | 3. Starta utvecklingsservern med `npm run dev`
12 |
13 | ## Funktioner
14 |
15 | Vår applikation inkluderar:
16 |
17 | - Realtidsdatasynkronisering
18 | - Stöd för flera språk
19 | - Responsiv design
20 | - Mörkt/Ljust temaläge
21 |
22 | ## Dokumentation
23 |
24 | För mer detaljerad information, kolla in:
25 |
26 | - [Installationsguide](./installation.md)
27 | - [API-referens](./api-reference.md)
28 | - [Bidragsriktlinjer](./contributing.md)
29 |
30 | ## Support
31 |
32 | Behöver du hjälp? Du kan:
33 |
34 | - Gå med i vår [Discord-gemenskap](https://discord.gg/example)
35 | - Öppna ett ärende på [GitHub](https://github.com/example/repo)
36 | - Maila oss på support@example.com
37 |
38 | ## Licens
39 |
40 | Detta projekt är licensierat under MIT-licensen - se [LICENS](./LICENSE)-filen för detaljer.
--------------------------------------------------------------------------------
/examples/namespace/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectId": "",
3 | "locale": {
4 | "source": "en",
5 | "targets": ["es", "fr", "de", "sv"]
6 | },
7 | "files": {
8 | "md": {
9 | "include": ["docs/[locale]/*.md"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/namespace/locales/en/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "common": {
3 | "hero": {
4 | "title": "This is a hero title",
5 | "description": "This is a hero description"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/namespace/locales/en/footer.json:
--------------------------------------------------------------------------------
1 | {
2 | "footer": {
3 | "links": {
4 | "home": "Home",
5 | "about": "About",
6 | "contact": "Contact"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/namespace/locales/en/header.json:
--------------------------------------------------------------------------------
1 | {
2 | "header": {
3 | "links": {
4 | "home": "Home",
5 | "about": "About",
6 | "contact": "Contact"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/namespace/locales/es/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "common": {
3 | "hero": {
4 | "title": "Este es un título de héroe",
5 | "description": "Esta es una descripción de héroe"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/namespace/locales/es/footer.json:
--------------------------------------------------------------------------------
1 | {
2 | "footer": {
3 | "links": {
4 | "home": "Inicio",
5 | "about": "Acerca de",
6 | "contact": "Contacto"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/namespace/locales/es/header.json:
--------------------------------------------------------------------------------
1 | {
2 | "header": {
3 | "links": {
4 | "home": "Inicio",
5 | "about": "Acerca de",
6 | "contact": "Contacto"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/namespace/locales/fr/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "common": {
3 | "hero": {
4 | "title": "Ceci est un titre de héros",
5 | "description": "Ceci est une description de héros"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/namespace/locales/fr/footer.json:
--------------------------------------------------------------------------------
1 | {
2 | "footer": {
3 | "links": {
4 | "home": "Accueil",
5 | "about": "À propos",
6 | "contact": "Contact"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/namespace/locales/fr/header.json:
--------------------------------------------------------------------------------
1 | {
2 | "header": {
3 | "links": {
4 | "home": "Accueil",
5 | "about": "À propos",
6 | "contact": "Contact"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/next-international/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["sv", "de", "fr", "it", "es", "pl", "no", "nl", "fi", "pt"]
5 | },
6 | "files": {
7 | "ts": {
8 | "include": ["locales/[locale].ts"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/next-international/languine.lock:
--------------------------------------------------------------------------------
1 | version: 1
2 | files:
3 | locales/en.ts:
4 | web.home.hero.announcement: ea5f65285f88f63e92e063ba338eb638
5 | web.home.hero.title: 87848172d8e4cfd6db9f6b7b455581a9
6 | web.home.hero.description: 6af7262a0bdcc4f277a65507537d1101
7 |
--------------------------------------------------------------------------------
/examples/next-international/locales/de.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Lesen Sie unseren neuesten Artikel",
6 | title: "Transformieren Sie Ihre Geschäftsabläufe noch heute",
7 | description: "In der heutigen schnelllebigen Welt verdient Ihr Unternehmen mehr als veraltete Handelssysteme. Unsere innovative Plattform optimiert die Abläufe, reduziert die Komplexität und hilft kleinen Unternehmen, in der modernen Wirtschaft erfolgreich zu sein."
8 | }
9 | }
10 | }
11 | } as const;
12 |
--------------------------------------------------------------------------------
/examples/next-international/locales/en.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Read our latest article",
6 | title: "Transform Your Business Operations Today",
7 | description:
8 | "In today's fast-paced world, your business deserves better than outdated trading systems. Our innovative platform streamlines operations, reduces complexity, and helps small businesses thrive in the modern economy.",
9 | },
10 | },
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/examples/next-international/locales/es.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Lee nuestro último artículo",
6 | title: "Transforma las operaciones de tu negocio hoy",
7 | description: "En el mundo acelerado de hoy, tu negocio merece algo mejor que sistemas de comercio obsoletos. Nuestra plataforma innovadora optimiza las operaciones, reduce la complejidad y ayuda a las pequeñas empresas a prosperar en la economía moderna."
8 | }
9 | }
10 | }
11 | } as const;
12 |
--------------------------------------------------------------------------------
/examples/next-international/locales/fi.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Lue viimeisin artikkelimme",
6 | title: "Muuta liiketoimintasi toimintoja tänään",
7 | description: "Nykyajan nopeassa maailmassa liiketoimintasi ansaitsee parempaa kuin vanhentuneet kaupankäyntijärjestelmät. Innovatiivinen alustamme virtaviivaistaa toimintoja, vähentää monimutkaisuutta ja auttaa pieniä yrityksiä menestymään nykyaikaisessa taloudessa."
8 | }
9 | }
10 | }
11 | } as const;
12 |
--------------------------------------------------------------------------------
/examples/next-international/locales/fr.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Lisez notre dernier article",
6 | title: "Transformez vos opérations commerciales dès aujourd'hui",
7 | description: "Dans le monde rapide d'aujourd'hui, votre entreprise mérite mieux que des systèmes de trading obsolètes. Notre plateforme innovante simplifie les opérations, réduit la complexité et aide les petites entreprises à prospérer dans l'économie moderne."
8 | }
9 | }
10 | }
11 | } as const;
12 |
--------------------------------------------------------------------------------
/examples/next-international/locales/it.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Leggi il nostro ultimo articolo",
6 | title: "Trasforma le tue operazioni aziendali oggi",
7 | description: "Nel mondo frenetico di oggi, la tua azienda merita di meglio rispetto ai sistemi di trading obsoleti. La nostra piattaforma innovativa semplifica le operazioni, riduce la complessità e aiuta le piccole imprese a prosperare nell'economia moderna."
8 | }
9 | }
10 | }
11 | } as const;
12 |
--------------------------------------------------------------------------------
/examples/next-international/locales/nl.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Lees ons nieuwste artikel",
6 | title: "Transformeer uw bedrijfsvoering vandaag nog",
7 | description: "In de snelle wereld van vandaag verdient uw bedrijf beter dan verouderde handelssystemen. Ons innovatieve platform stroomlijnt de operaties, vermindert complexiteit en helpt kleine bedrijven te gedijen in de moderne economie."
8 | }
9 | }
10 | }
11 | } as const;
12 |
--------------------------------------------------------------------------------
/examples/next-international/locales/no.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Les vår nyeste artikkel",
6 | title: "Transformér dine forretningsoperasjoner i dag",
7 | description: "I dagens raske verden fortjener virksomheten din bedre enn utdaterte handelssystemer. Vår innovative plattform strømlinjeformer operasjoner, reduserer kompleksitet og hjelper små bedrifter med å blomstre i den moderne økonomien."
8 | }
9 | }
10 | }
11 | } as const;
12 |
--------------------------------------------------------------------------------
/examples/next-international/locales/pl.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Przeczytaj nasz najnowszy artykuł",
6 | title: "Przekształć swoje operacje biznesowe już dziś",
7 | description: "W dzisiejszym szybkim świecie, Twoja firma zasługuje na więcej niż przestarzałe systemy handlowe. Nasza innowacyjna platforma upraszcza operacje, redukuje złożoność i pomaga małym firmom prosperować w nowoczesnej gospodarce."
8 | }
9 | }
10 | }
11 | } as const;
12 |
--------------------------------------------------------------------------------
/examples/next-international/locales/pt.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Leia nosso artigo mais recente",
6 | title: "Transforme as Operações da Sua Empresa Hoje",
7 | description: "No mundo acelerado de hoje, sua empresa merece mais do que sistemas de comércio ultrapassados. Nossa plataforma inovadora simplifica operações, reduz a complexidade e ajuda pequenas empresas a prosperar na economia moderna."
8 | }
9 | }
10 | }
11 | } as const;
12 |
--------------------------------------------------------------------------------
/examples/next-international/locales/sv.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | web: {
3 | home: {
4 | hero: {
5 | announcement: "Läs vår senaste artikel",
6 | title: "Transformera dina affärsverksamheter idag",
7 | description: "I dagens snabba värld förtjänar ditt företag bättre än föråldrade handelssystem. Vår innovativa plattform strömlinjeformar verksamheten, minskar komplexiteten och hjälper små företag att blomstra i den moderna ekonomin."
8 | }
9 | }
10 | }
11 | } as const;
12 |
--------------------------------------------------------------------------------
/examples/next-intl/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["de"]
5 | },
6 | "files": {
7 | "json": {
8 | "include": ["messages/[locale].json"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/nuxt/i18n/locales/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": {
3 | "title": "Seite - {title}"
4 | },
5 | "pages": {
6 | "title": {
7 | "top": "Oben",
8 | "about": "Über diese Seite"
9 | }
10 | },
11 | "snakeCaseText": "@.snakeCase:{'pages.title.about'}",
12 | "pascalCaseText": "@.pascalCase:{'pages.title.about'}",
13 | "welcome": "Willkommen",
14 | "hello": "Hallo {name} !",
15 | "tag": "Tag
",
16 | "items": {
17 | "0": {
18 | "name": "Apfel"
19 | },
20 | "1": {
21 | "name": "Banane"
22 | },
23 | "2": {
24 | "name": "Erdbeere"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/nuxt/i18n/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": {
3 | "title": "Page - {title}"
4 | },
5 | "pages": {
6 | "title": {
7 | "top": "Top",
8 | "about": "About this site"
9 | }
10 | },
11 | "snakeCaseText": "@.snakeCase:{'pages.title.about'}",
12 | "pascalCaseText": "@.pascalCase:{'pages.title.about'}",
13 | "welcome": "Welcome",
14 | "hello": "Hello {name} !",
15 | "tag": "Tag
",
16 | "items": [
17 | {
18 | "name": "apple"
19 | },
20 | {
21 | "name": "banana"
22 | },
23 | {
24 | "name": "strawberry"
25 | }
26 | ]
27 | }
--------------------------------------------------------------------------------
/examples/nuxt/i18n/locales/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": {
3 | "title": "Página - {title}"
4 | },
5 | "pages": {
6 | "title": {
7 | "top": "Superior",
8 | "about": "Acerca de este sitio"
9 | }
10 | },
11 | "snakeCaseText": "@.snakeCase:{'pages.title.about'}",
12 | "pascalCaseText": "@.pascalCase:{'pages.title.about'}",
13 | "welcome": "Bienvenido",
14 | "hello": "¡Hola {name}!",
15 | "tag": "Etiqueta
",
16 | "items": {
17 | "0": {
18 | "name": "manzana"
19 | },
20 | "1": {
21 | "name": "plátano"
22 | },
23 | "2": {
24 | "name": "fresa"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/nuxt/i18n/locales/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": {
3 | "title": "Page - {title}"
4 | },
5 | "pages": {
6 | "title": {
7 | "top": "Haut",
8 | "about": "À propos de ce site"
9 | }
10 | },
11 | "snakeCaseText": "@.snakeCase:{'pages.title.about'}",
12 | "pascalCaseText": "@.pascalCase:{'pages.title.about'}",
13 | "welcome": "Bienvenue",
14 | "hello": "Bonjour {name} !",
15 | "tag": "Étiquette
",
16 | "items": {
17 | "0": {
18 | "name": "pomme"
19 | },
20 | "1": {
21 | "name": "banane"
22 | },
23 | "2": {
24 | "name": "fraise"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/nuxt/i18n/locales/sv.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": {
3 | "title": "Sida - {title}"
4 | },
5 | "pages": {
6 | "title": {
7 | "top": "Topp",
8 | "about": "Om denna webbplats"
9 | }
10 | },
11 | "snakeCaseText": "@.snakeCase:{'pages.title.about'}",
12 | "pascalCaseText": "@.pascalCase:{'pages.title.about'}",
13 | "welcome": "Välkommen",
14 | "hello": "Hej {name} !",
15 | "tag": "Tagg
",
16 | "items": {
17 | "0": {
18 | "name": "äpple"
19 | },
20 | "1": {
21 | "name": "banan"
22 | },
23 | "2": {
24 | "name": "jordgubbe"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/nuxt/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectId": "prj_cfqrijaze5fcndopqzh9ljwp",
3 | "locale": {
4 | "source": "en",
5 | "targets": ["es", "fr", "de", "sv"]
6 | },
7 | "files": {
8 | "json": {
9 | "include": ["src/i18n/locales/[locale].json"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/nuxt/src/i18n/locales/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": {
3 | "title": "Seite - {title}"
4 | },
5 | "pages": {
6 | "title": {
7 | "top": "Oben",
8 | "about": "Über diese Seite"
9 | }
10 | },
11 | "snakeCaseText": "@.snakeCase:{'pages.title.about'}",
12 | "pascalCaseText": "@.pascalCase:{'pages.title.about'}",
13 | "welcome": "Willkommen",
14 | "hello": "Hallo {name} !",
15 | "tag": "Tag
",
16 | "items": {
17 | "0": {
18 | "name": "Apfel"
19 | },
20 | "1": {
21 | "name": "Banane"
22 | },
23 | "2": {
24 | "name": "Erdbeere"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/nuxt/src/i18n/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": {
3 | "title": "Page - {title}"
4 | },
5 | "pages": {
6 | "title": {
7 | "top": "Top",
8 | "about": "About this site"
9 | }
10 | },
11 | "snakeCaseText": "@.snakeCase:{'pages.title.about'}",
12 | "pascalCaseText": "@.pascalCase:{'pages.title.about'}",
13 | "welcome": "Welcome",
14 | "hello": "Hello {name} !",
15 | "tag": "Tag
",
16 | "items": [
17 | {
18 | "name": "apple"
19 | },
20 | {
21 | "name": "banana"
22 | },
23 | {
24 | "name": "strawberry"
25 | }
26 | ]
27 | }
--------------------------------------------------------------------------------
/examples/nuxt/src/i18n/locales/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": {
3 | "title": "Página - {title}"
4 | },
5 | "pages": {
6 | "title": {
7 | "top": "Superior",
8 | "about": "Acerca de este sitio"
9 | }
10 | },
11 | "snakeCaseText": "@.snakeCase:{'pages.title.about'}",
12 | "pascalCaseText": "@.pascalCase:{'pages.title.about'}",
13 | "welcome": "Bienvenido",
14 | "hello": "¡Hola {name}!",
15 | "tag": "Etiqueta
",
16 | "items": {
17 | "0": {
18 | "name": "manzana"
19 | },
20 | "1": {
21 | "name": "plátano"
22 | },
23 | "2": {
24 | "name": "fresa"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/nuxt/src/i18n/locales/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": {
3 | "title": "Page - {title}"
4 | },
5 | "pages": {
6 | "title": {
7 | "top": "Haut",
8 | "about": "À propos de ce site"
9 | }
10 | },
11 | "snakeCaseText": "@.snakeCase:{'pages.title.about'}",
12 | "pascalCaseText": "@.pascalCase:{'pages.title.about'}",
13 | "welcome": "Bienvenue",
14 | "hello": "Bonjour {name} !",
15 | "tag": "Étiquette
",
16 | "items": {
17 | "0": {
18 | "name": "pomme"
19 | },
20 | "1": {
21 | "name": "banane"
22 | },
23 | "2": {
24 | "name": "fraise"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/nuxt/src/i18n/locales/sv.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": {
3 | "title": "Sida - {title}"
4 | },
5 | "pages": {
6 | "title": {
7 | "top": "Topp",
8 | "about": "Om denna webbplats"
9 | }
10 | },
11 | "snakeCaseText": "@.snakeCase:{'pages.title.about'}",
12 | "pascalCaseText": "@.pascalCase:{'pages.title.about'}",
13 | "welcome": "Välkommen",
14 | "hello": "Hej {name} !",
15 | "tag": "Tagg
",
16 | "items": {
17 | "0": {
18 | "name": "äpple"
19 | },
20 | "1": {
21 | "name": "banan"
22 | },
23 | "2": {
24 | "name": "jordgubbe"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/overrides/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": [
5 | "ru"
6 | ]
7 | },
8 | "files": {
9 | "json": {
10 | "include": [
11 | "src/locales/[locale].json"
12 | ]
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/examples/overrides/languine.lock:
--------------------------------------------------------------------------------
1 | version: 1
2 | files:
3 | src/assets/translations/en.json:
4 | adcreative\.screen.generate\.card\.desc: 659254265e02c2581f5b65eac56874b8
5 | adcreative\.screen.title: c95c91210e835d0f0260015b681d8225
6 | src/locales/en.json:
7 | adcreative\.screen.generate\.card\.desc: 659254265e02c2581f5b65eac56874b8
8 | adcreative\.screen.title: c95c91210e835d0f0260015b681d8225
9 |
--------------------------------------------------------------------------------
/examples/overrides/src/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "adcreative.screen": {
3 | "generate.card.desc": "Generate conversion-focused ads in seconds with our AI.",
4 | "title": "Ad Creatives"
5 | }
6 | }
--------------------------------------------------------------------------------
/examples/overrides/src/locales/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "adcreative.screen": {
3 | "generate.card.desc": "Создавайте рекламу, ориентированную на конверсии, за считанные секунды с помощью нашего ИИ.",
4 | "title": "Рекламные креативы"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@languine/example",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "languine": "^2.0.5"
6 | },
7 | "devDependencies": {
8 | "@biomejs/js-api": "^0.7.1",
9 | "@biomejs/wasm-nodejs": "^1.9.4"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/php/lang/en/common.php:
--------------------------------------------------------------------------------
1 | 'Hello, world!',
5 | 'greeting' => 'Hello, :name!',
6 | 'welcome' => 'Welcome to our application!',
7 | 'error' => 'An error occurred: :message',
8 | 'success' => 'Operation completed successfully.',
9 | 'confirmation' => 'Are you sure you want to :action?',
10 | 'instructions' => 'Please follow these steps to :action:',
11 | 'time' => [
12 | 'today' => 'Today',
13 | 'yesterday' => 'Yesterday',
14 | 'tomorrow' => 'Tomorrow',
15 | ],
16 | ];
17 |
--------------------------------------------------------------------------------
/examples/php/lang/es/common.php:
--------------------------------------------------------------------------------
1 | '¡Hola, mundo!',
7 | 'greeting' => '¡Hola, :name!',
8 | 'welcome' => '¡Bienvenido a nuestra aplicación!',
9 | 'error' => 'Ocurrió un error: :message',
10 | 'success' => 'Operación completada con éxito.',
11 | 'confirmation' => '¿Estás seguro de que quieres :action?',
12 | 'instructions' => 'Por favor, sigue estos pasos para :action:',
13 | 'time' => [
14 | 'today' => 'Hoy',
15 | 'yesterday' => 'Ayer',
16 | 'tomorrow' => 'Mañana'
17 | ]
18 | ];
19 |
--------------------------------------------------------------------------------
/examples/php/lang/sv/common.php:
--------------------------------------------------------------------------------
1 | 'Hej, världen!',
7 | 'greeting' => 'Hej, :name!',
8 | 'welcome' => 'Välkommen till vår applikation!',
9 | 'error' => 'Ett fel inträffade: :message',
10 | 'success' => 'Operationen slutfördes framgångsrikt.',
11 | 'confirmation' => 'Är du säker på att du vill :action?',
12 | 'instructions' => 'Vänligen följ dessa steg för att :action:',
13 | 'time' => [
14 | 'today' => 'Idag',
15 | 'yesterday' => 'Igår',
16 | 'tomorrow' => 'Imorgon'
17 | ]
18 | ];
19 |
--------------------------------------------------------------------------------
/examples/php/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["es", "sv"]
5 | },
6 | "files": {
7 | "php": {
8 | "include": ["lang/[locale]/*.php"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/php/languine.lock:
--------------------------------------------------------------------------------
1 | version: 1
2 | files:
3 | lang/en/common.php:
4 | hello: 6cd3556deb0da54bca060b4c39479839
5 | greeting: 281b672d3c2ed3de06a9f76734ce0017
6 | welcome: 91a328d124ccee9f8e7d1f37fd1a6776
7 | error: e838d117cfeb17601c8a34f2bca6d3a6
8 | success: a144eccbfa0dcd6afc57b0d7e3b2fceb
9 | confirmation: 00dff16d0f293a900355d2ed9041f97d
10 | instructions: 8890e41e6bc13eba0fb9283142b76774
11 | time.today: 1dd1c5fb7f25cd41b291d43a89e3aefd
12 | time.yesterday: ebfe9ce86e6e9fb953aa7a25b59c1956
13 | time.tomorrow: ff9eb8bd8c927da073cea632f294e23b
14 |
--------------------------------------------------------------------------------
/examples/po/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["es"]
5 | },
6 | "files": {
7 | "po": {
8 | "include": ["locales/[locale].po"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/po/locales/en.po:
--------------------------------------------------------------------------------
1 | # This is a PO file containing English strings that serve as the source for translations.
2 | # Each entry consists of a msgid (string identifier) and msgstr (translated string).
3 | msgid "welcome_message"
4 | msgstr "Welcome to our application!"
5 |
6 | msgid "error_message"
7 | msgstr "An error occurred: {error_code}"
8 |
9 | msgid "example_message"
10 | msgstr "This is an example message with a {variable}"
11 |
--------------------------------------------------------------------------------
/examples/po/locales/es.po:
--------------------------------------------------------------------------------
1 | msgid "welcome_message"
2 | msgstr "¡Bienvenido a nuestra aplicación!"
3 |
4 | msgid "error_message"
5 | msgstr "Ocurrió un error: {error_code}"
6 |
7 | msgid "example_message"
8 | msgstr "Este es un mensaje de ejemplo con un {variable}"
9 |
--------------------------------------------------------------------------------
/examples/react-email/emails/static/vercel-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/react-email/emails/static/vercel-arrow.png
--------------------------------------------------------------------------------
/examples/react-email/emails/static/vercel-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/react-email/emails/static/vercel-logo.png
--------------------------------------------------------------------------------
/examples/react-email/emails/static/vercel-team.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/react-email/emails/static/vercel-team.png
--------------------------------------------------------------------------------
/examples/react-email/emails/static/vercel-user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/examples/react-email/emails/static/vercel-user.png
--------------------------------------------------------------------------------
/examples/react-email/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["es", "sv", "pt"]
5 | },
6 | "files": {
7 | "json": {
8 | "include": ["locales/[locale].json"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/react-email/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "previewText": "Join %{invitedByUsername} on %{company}",
3 | "company": "%{company}",
4 | "logoAlt": "Vercel Logo",
5 | "joinTeamHeading": "Join %{teamName} on %{company}",
6 | "greeting": "Hi %{username},",
7 | "invitationText": "%{invitedByUsername} (%{email}) has invited you to join the %{teamName} team on %{company}.",
8 | "invitedToAlt": "Invited to",
9 | "joinTeamButton": "Join the team",
10 | "copyUrlText": "Or copy and paste this URL into your browser:",
11 | "footerText": "This invitation was intended for %{username} (%{ip} from %{location}). If you were not expecting this invitation, you can ignore this email. If you are concerned about your account's safety, please reply to this email to get in touch with us."
12 | }
--------------------------------------------------------------------------------
/examples/react-email/locales/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "previewText": "Únete a %{invitedByUsername} en %{company}",
3 | "company": "%{company}",
4 | "logoAlt": "Logo de Vercel",
5 | "joinTeamHeading": "Únete a %{teamName} en %{company}",
6 | "greeting": "Hola %{username},",
7 | "invitationText": "%{invitedByUsername} (%{email}) te ha invitado a unirte al equipo %{teamName} en %{company}.",
8 | "invitedToAlt": "Invitado a",
9 | "joinTeamButton": "Únete al equipo",
10 | "copyUrlText": "O copia y pega esta URL en tu navegador:",
11 | "footerText": "Esta invitación estaba destinada a %{username} (%{ip} desde %{location}). Si no esperabas esta invitación, puedes ignorar este correo electrónico. Si estás preocupado por la seguridad de tu cuenta, por favor responde a este correo electrónico para ponerte en contacto con nosotros."
12 | }
13 |
--------------------------------------------------------------------------------
/examples/react-email/locales/pt.json:
--------------------------------------------------------------------------------
1 | {
2 | "previewText": "Junte-se a %{invitedByUsername} no %{company}",
3 | "company": "%{company}",
4 | "logoAlt": "Logo da Vercel",
5 | "joinTeamHeading": "Junte-se a %{teamName} no %{company}",
6 | "greeting": "Olá %{username},",
7 | "invitationText": "%{invitedByUsername} (%{email}) convidou você para se juntar à equipe %{teamName} no %{company}.",
8 | "invitedToAlt": "Convidado para",
9 | "joinTeamButton": "Junte-se à equipe",
10 | "copyUrlText": "Ou copie e cole este URL no seu navegador:",
11 | "footerText": "Este convite foi destinado a %{username} (%{ip} de %{location}). Se você não estava esperando por este convite, pode ignorar este e-mail. Se você está preocupado com a segurança da sua conta, por favor, responda a este e-mail para entrar em contato conosco."
12 | }
13 |
--------------------------------------------------------------------------------
/examples/react-email/locales/sv.json:
--------------------------------------------------------------------------------
1 | {
2 | "previewText": "Gå med %{invitedByUsername} på %{company}",
3 | "company": "%{company}",
4 | "logoAlt": "Vercel-logotyp",
5 | "joinTeamHeading": "Gå med %{teamName} på %{company}",
6 | "greeting": "Hej %{username},",
7 | "invitationText": "%{invitedByUsername} (%{email}) har bjudit in dig att gå med i %{teamName}-teamet på %{company}.",
8 | "invitedToAlt": "Inbjuden till",
9 | "joinTeamButton": "Gå med i teamet",
10 | "copyUrlText": "Eller kopiera och klistra in denna URL i din webbläsare:",
11 | "footerText": "Denna inbjudan var avsedd för %{username} (%{ip} från %{location}). Om du inte förväntade dig denna inbjudan kan du ignorera detta e-postmeddelande. Om du är orolig för din kontos säkerhet, vänligen svara på detta e-postmeddelande för att komma i kontakt med oss."
12 | }
13 |
--------------------------------------------------------------------------------
/examples/react-email/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-email-starter",
3 | "version": "0.1.6",
4 | "private": true,
5 | "scripts": {
6 | "build": "email build",
7 | "dev": "email dev",
8 | "export": "email export"
9 | },
10 | "dependencies": {
11 | "@react-email/components": "0.0.31",
12 | "react": "19.0.0",
13 | "react-dom": "19.0.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "19.0.1",
17 | "@types/react-dom": "19.0.1",
18 | "react-email": "3.0.4"
19 | }
20 | }
--------------------------------------------------------------------------------
/examples/react-email/readme.md:
--------------------------------------------------------------------------------
1 | # React Email Starter
2 |
3 | A live preview right in your browser so you don't need to keep sending real emails during development.
4 |
5 | ## Getting Started
6 |
7 | First, install the dependencies:
8 |
9 | ```sh
10 | npm install
11 | # or
12 | yarn
13 | ```
14 |
15 | Then, run the development server:
16 |
17 | ```sh
18 | npm run dev
19 | # or
20 | yarn dev
21 | ```
22 |
23 | Open [localhost:3000](http://localhost:3000) with your browser to see the result.
24 |
25 | ## License
26 |
27 | MIT License
28 |
--------------------------------------------------------------------------------
/examples/react-i18next/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["es"]
5 | },
6 | "files": {
7 | "json": {
8 | "include": ["locales/[locale].json"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/react-i18next/languine.lock:
--------------------------------------------------------------------------------
1 | version: 1
2 | files:
3 | locales/en.json:
4 | welcome: 91a328d124ccee9f8e7d1f37fd1a6776
5 | user.greeting: 05cab6b3c28feaccc7d7b5c5158a2eda
6 | user.profile.title: 3d8de6fcc09baf73f7175fe59f278ced
7 | user.profile.edit: dfd87bd34b4599aaaef6d0f2c8b6e3d7
8 | notifications.messages: 9bedb26e51829d23bd8545dba50e361d
9 | notifications.empty: 7d0cb81666069504046f678bb63aae39
10 |
--------------------------------------------------------------------------------
/examples/react-i18next/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": "Welcome to our application!",
3 | "user": {
4 | "greeting": "Hi, {{name}}!",
5 | "profile": {
6 | "title": "Your profile",
7 | "edit": "Edit profile"
8 | }
9 | },
10 | "notifications": {
11 | "messages": "You have {{count}} new message(s).",
12 | "empty": "No new notifications."
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/react-i18next/locales/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": "¡Bienvenido a nuestra aplicación!",
3 | "user": {
4 | "greeting": "¡Hola, {{name}}!",
5 | "profile": {
6 | "title": "Tu perfil",
7 | "edit": "Editar perfil"
8 | }
9 | },
10 | "notifications": {
11 | "messages": "Tienes {{count}} nuevo(s) mensaje(s).",
12 | "empty": "No hay nuevas notificaciones."
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/react-i18next/locales/sv.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": "Välkommen till vår applikation!",
3 | "user": {
4 | "greeting": "Hej, {{name}}!",
5 | "profile": {
6 | "title": "Din profil",
7 | "edit": "Redigera profil"
8 | }
9 | },
10 | "notifications": {
11 | "messages": "Du har {{count}} nytt meddelande(n).",
12 | "empty": "Inga nya notiser."
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/transform/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectId": "prj_sg99wnzulvduq0necacvjl9a",
3 | "locale": {
4 | "source": "en",
5 | "targets": [
6 | "es"
7 | ]
8 | },
9 | "files": {
10 | "json": {
11 | "include": [
12 | "src/locales/[locale].json"
13 | ]
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/transform/src/components/header.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export function Header() {
4 | return (
5 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/examples/transform/src/components/hero.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export function Hero() {
4 | return (
5 |
6 |
This is a hero text about our startup!
7 |
And this is the best description about it.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
![This is a great hero of our startup]()
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/xcode-strings/Example/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "welcome_message" = "Welcome to Weather App!";
2 | "temperature_format" = "%1$.1f°%2$@";
3 | "wind_speed" = "Wind: %1$.1f %2$@";
4 | "forecast_daily" = "Daily forecast for %@";
5 | "notification_body" = "Storm alert for %1$@ region\nWind speeds up to %2$d mph expected";
6 |
7 | "battery_empty" = "Battery depleted";
8 | "battery_low" = "Battery at %d%% - please charge soon";
9 | "battery_full" = "Battery fully charged";
10 |
11 | "photo_count_zero" = "No photos";
12 | "photo_count_one" = "%d photo";
13 | "photo_count_other" = "%d photos";
14 |
15 | "time_remaining_now" = "Download complete!";
16 | "time_remaining_minutes" = "%d minutes remaining";
17 | "time_remaining_hours" = "About %d hours remaining";
18 |
19 | "share_message" = "Check out this photo I took with %1$@!\nLocation: %2$@\nTime: %3$@";
20 | "rich_notification" = "New message from %@:\n%@\nView details";
21 |
22 | "hello" = "Hello";
--------------------------------------------------------------------------------
/examples/xcode-strings/Example/es.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "welcome_message" = "¡Bienvenido a la aplicación del clima!";
2 | "temperature_format" = "%1$.1f°%2$@";
3 | "wind_speed" = "Viento: %1$.1f %2$@";
4 | "forecast_daily" = "Pronóstico diario para %@";
5 | "notification_body" = "Alerta de tormenta para la región %1$@\nSe esperan velocidades de viento de hasta %2$d mph";
6 | "battery_empty" = "Batería agotada";
7 | "battery_low" = "Batería al %d%% - por favor, carga pronto";
8 | "battery_full" = "Batería completamente cargada";
9 | "photo_count_zero" = "Sin fotos";
10 | "photo_count_one" = "%d foto";
11 | "photo_count_other" = "%d fotos";
12 | "time_remaining_now" = "¡Descarga completa!";
13 | "time_remaining_minutes" = "%d minutos restantes";
14 | "time_remaining_hours" = "Acerca de %d horas restantes";
15 | "share_message" = "¡Mira esta foto que tomé con %1$@!\nUbicación: %2$@\nHora: %3$@";
16 | "rich_notification" = "Nuevo mensaje de %@:\n%@\nVer detalles";
17 | "hello" = "Hola";
18 |
--------------------------------------------------------------------------------
/examples/xcode-strings/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["es"]
5 | },
6 | "files": {
7 | "xcode-strings": {
8 | "include": ["Example/[locale].lproj/Localizable.strings"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/xcode-stringsdict/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["es", "de"]
5 | },
6 | "files": {
7 | "xcode-stringsdict": {
8 | "include": ["Example/[locale].lproj/Localizable.stringsdict"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/xcode-xcstrings/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["de", "fr"]
5 | },
6 | "files": {
7 | "xcode-xcstrings": {
8 | "include": ["Example/Localizable.xcstrings"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/yaml/languine.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": {
3 | "source": "en",
4 | "targets": ["es", "fr"]
5 | },
6 | "files": {
7 | "yaml": {
8 | "include": ["locales/[locale].yml"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/yaml/locales/en.yml:
--------------------------------------------------------------------------------
1 | # General messages
2 | welcome: Welcome to our application!
3 | goodbye: Thanks for using our app. See you next time!
4 |
5 | # Navigation
6 | nav:
7 | home: Home
8 | about: About
9 | contact: Contact Us
10 | settings: Settings
11 |
12 | # User messages
13 | user:
14 | greeting: "Hello, {name}!"
15 | profile:
16 | title: Your Profile
17 | edit: Edit Profile
18 | save: Save Changes
19 | cancel: Cancel
20 |
21 | # Error messages
22 | errors:
23 | not_found: Page not found
24 | server_error: An error occurred on the server
25 | validation:
26 | required: This field is required
27 | email: Please enter a valid email address
28 | password: Password must be at least 8 characters
29 |
30 | # Form labels
31 | form:
32 | email: Email Address
33 | password: Password
34 | submit: Submit
35 | reset: Reset Form
36 |
37 | # Success messages
38 | success:
39 | saved: Changes saved successfully
40 | uploaded: File uploaded successfully
41 | deleted: Item deleted successfully
42 |
43 | # Time-related
44 | time:
45 | today: Today
46 | yesterday: Yesterday
47 | tomorrow: Tomorrow
48 | days_ago: "{count} days ago"
--------------------------------------------------------------------------------
/examples/yaml/locales/es.yml:
--------------------------------------------------------------------------------
1 | welcome: ¡Bienvenido a nuestra aplicación!
2 | goodbye: Gracias por usar nuestra aplicación. ¡Hasta la próxima!
3 | nav:
4 | home: Inicio
5 | about: Acerca de
6 | contact: Contáctanos
7 | settings: Configuración
8 | user:
9 | greeting: Hola, {name}!
10 | profile:
11 | title: Tu Perfil
12 | edit: Editar Perfil
13 | save: Guardar Cambios
14 | cancel: Cancelar
15 | errors:
16 | not_found: Página no encontrada
17 | server_error: Ocurrió un error en el servidor
18 | validation:
19 | required: Este campo es obligatorio
20 | email: Por favor, introduce una dirección de correo electrónico válida
21 | password: La contraseña debe tener al menos 8 caracteres
22 | form:
23 | email: Dirección de Correo Electrónico
24 | password: Contraseña
25 | submit: Enviar
26 | reset: Reiniciar Formulario
27 | success:
28 | saved: Cambios guardados exitosamente
29 | uploaded: Archivo subido exitosamente
30 | deleted: Elemento eliminado exitosamente
31 | time:
32 | today: Hoy
33 | yesterday: Ayer
34 | tomorrow: Mañana
35 | days_ago: Hace {count} días
36 |
--------------------------------------------------------------------------------
/examples/yaml/locales/fr.yml:
--------------------------------------------------------------------------------
1 | welcome: Bienvenue dans notre application !
2 | goodbye: Merci d'utiliser notre application. À la prochaine !
3 | nav:
4 | home: Accueil
5 | about: À propos
6 | contact: Contactez-nous
7 | settings: Paramètres
8 | user:
9 | greeting: Bonjour, {name} !
10 | profile:
11 | title: Votre profil
12 | edit: Modifier le profil
13 | save: Enregistrer les modifications
14 | cancel: Annuler
15 | errors:
16 | not_found: Page non trouvée
17 | server_error: Une erreur s'est produite sur le serveur
18 | validation:
19 | required: Ce champ est requis
20 | email: Veuillez entrer une adresse e-mail valide
21 | password: Le mot de passe doit contenir au moins 8 caractères
22 | form:
23 | email: Adresse e-mail
24 | password: Mot de passe
25 | submit: Soumettre
26 | reset: Réinitialiser le formulaire
27 | success:
28 | saved: Modifications enregistrées avec succès
29 | uploaded: Fichier téléchargé avec succès
30 | deleted: Élément supprimé avec succès
31 | time:
32 | today: Aujourd'hui
33 | yesterday: Hier
34 | tomorrow: Demain
35 | days_ago: Il y a {count} jours
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@languine/core",
3 | "version": "1.0.0",
4 | "private": true,
5 | "workspaces": ["packages/*", "apps/*", "examples/*"],
6 | "license": "MIT",
7 | "scripts": {
8 | "build": "turbo build",
9 | "clean": "git clean -xdf node_modules",
10 | "clean:workspaces": "turbo clean",
11 | "dev": "turbo dev --parallel",
12 | "format": "biome format --write .",
13 | "lint": "turbo lint && manypkg check",
14 | "typecheck": "turbo typecheck"
15 | },
16 | "packageManager": "bun@1.1.42",
17 | "devDependencies": {
18 | "@biomejs/biome": "^1.9.4",
19 | "@changesets/changelog-github": "^0.5.0",
20 | "@changesets/cli": "^2.27.12",
21 | "@types/bun": "^1.2.2",
22 | "turbo": "2.4.0",
23 | "typescript": "^5.7.3"
24 | },
25 | "dependencies": {
26 | "@clack/prompts": "^0.9.1",
27 | "expo-localization": "~16.0.1",
28 | "i18n-js": "^4.5.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/action/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM oven/bun:1
2 |
3 | # Install git
4 | RUN apt-get update && apt-get install -y git
5 |
6 | # Create and set working directory
7 | WORKDIR /action
8 |
9 | # Copy package files, lockfile and config files for the action
10 | COPY bun.lock package.json tsconfig.json /action/
11 |
12 | # Copy action source code
13 | COPY src /action/src
14 |
15 | # Install action dependencies
16 | RUN bun install --frozen-lockfile
17 |
18 | # Run action
19 | ENTRYPOINT ["bun", "run", "/action/src/index.ts"]
20 |
21 |
--------------------------------------------------------------------------------
/packages/action/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@languine/action",
3 | "version": "0.0.1",
4 | "type": "module",
5 | "main": "src/index.ts",
6 | "scripts": {
7 | "clean": "rm -rf .turbo node_modules dist",
8 | "lint": "biome check .",
9 | "format": "biome format --write .",
10 | "typecheck": "tsc --noEmit",
11 | "test": "bun test"
12 | },
13 | "dependencies": {
14 | "@clack/prompts": "^0.7.0",
15 | "octokit": "^4.1.0",
16 | "tsup": "^8.3.6",
17 | "zod": "^3.24.1"
18 | },
19 | "devDependencies": {
20 | "typescript": "^5.7.3"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/action/src/index.ts:
--------------------------------------------------------------------------------
1 | import { GitProviderFactory } from "./platforms/git-provider-factory.ts";
2 | import { TranslationService } from "./services/translation.ts";
3 | import { parseConfig } from "./utils/config.ts";
4 | import { logger } from "./utils/logger.ts";
5 | import { WorkflowFactory } from "./workflows/factory.ts";
6 |
7 | async function main() {
8 | try {
9 | const config = parseConfig();
10 |
11 | const gitProvider = GitProviderFactory.getInstance().getProvider();
12 | const translationService = new TranslationService();
13 |
14 | const workflow = new WorkflowFactory(
15 | gitProvider,
16 | translationService,
17 | config,
18 | );
19 |
20 | await workflow.run();
21 | } catch (error) {
22 | logger.error(
23 | error instanceof Error ? error.message : "Unknown error occurred",
24 | );
25 | process.exit(1);
26 | }
27 | }
28 |
29 | main();
30 |
--------------------------------------------------------------------------------
/packages/action/src/platforms/git-provider-factory.ts:
--------------------------------------------------------------------------------
1 | import type { GitPlatform } from "../types.ts";
2 | import { GitHubProvider } from "./github.ts";
3 |
4 | /**
5 | * Factory class for creating Git provider instances.
6 | * Currently supports GitHub, but can be extended for other platforms.
7 | */
8 | export class GitProviderFactory {
9 | private static instance: GitProviderFactory;
10 | private provider: GitPlatform;
11 |
12 | private constructor() {
13 | // For now, we only support GitHub
14 | this.provider = new GitHubProvider();
15 | }
16 |
17 | /**
18 | * Get the singleton instance of the factory
19 | */
20 | public static getInstance(): GitProviderFactory {
21 | if (!GitProviderFactory.instance) {
22 | GitProviderFactory.instance = new GitProviderFactory();
23 | }
24 |
25 | return GitProviderFactory.instance;
26 | }
27 |
28 | /**
29 | * Get the Git provider instance
30 | */
31 | public getProvider(): GitPlatform {
32 | return this.provider;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/action/src/services/translation.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "../utils/config.ts";
2 | import { runCommand } from "../utils/exec.ts";
3 | import { logger } from "../utils/logger.ts";
4 |
5 | interface ExecError extends Error {
6 | stderr?: string;
7 | }
8 |
9 | export class TranslationService {
10 | #getCliCommand(cliVersion = "latest") {
11 | return `languine@${cliVersion}`;
12 | }
13 |
14 | async runTranslation(config: Config) {
15 | try {
16 | const { apiKey, projectId, cliVersion, workingDirectory } = config;
17 |
18 | const cliCommand = this.#getCliCommand(cliVersion);
19 |
20 | logger.debug(`CLI Command: bun x ${cliCommand}`);
21 | logger.debug(`Project ID: ${projectId}`);
22 | logger.debug(`CLI Version: ${cliVersion}`);
23 | logger.debug(`Working Directory: ${process.cwd()}`);
24 |
25 | await runCommand([
26 | "bunx",
27 | cliCommand,
28 | "translate",
29 | "--project-id",
30 | projectId,
31 | "--api-key",
32 | apiKey,
33 | ]);
34 | } catch (error) {
35 | logger.error(`Translation process failed: ${error}`);
36 | throw error;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/action/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "pretty": true,
5 | "esModuleInterop": true,
6 | "resolveJsonModule": true,
7 | "allowSyntheticDefaultImports": true,
8 | "skipLibCheck": true,
9 | "skipDefaultLibCheck": true,
10 | "moduleResolution": "NodeNext",
11 | "module": "NodeNext",
12 | "target": "ESNext",
13 | "rootDir": ".",
14 | "allowImportingTsExtensions": true,
15 | "noEmit": true
16 | },
17 | "include": ["**/*.ts"]
18 | }
--------------------------------------------------------------------------------
/packages/action/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { PlatformConfig } from "./platforms/provider.ts";
2 |
3 | export interface GitPlatform {
4 | getPlatformConfig(): PlatformConfig;
5 | setupGit(): Promise;
6 | createOrUpdatePullRequest(options: {
7 | title: string;
8 | body: string;
9 | branch: string;
10 | }): Promise;
11 | getCurrentBranch(): Promise;
12 | pullAndRebase(branch: string): Promise;
13 | commitAndPush(options: {
14 | message: string;
15 | branch: string;
16 | }): Promise;
17 | createBranch(branchName: string): Promise;
18 | addChanges(): Promise;
19 | hasChanges(): Promise;
20 | checkBotCommit(): Promise;
21 | getOpenPullRequestNumber(branch: string): Promise;
22 | closeOpenPullRequest(options: { pullRequestNumber: number }): Promise;
23 | addCommentToPullRequest(options: {
24 | pullRequestNumber: number;
25 | body: string;
26 | }): Promise;
27 | }
28 |
29 | export interface GitWorkflow {
30 | preRun(): Promise;
31 | run(): Promise;
32 | postRun(): Promise;
33 | }
34 |
--------------------------------------------------------------------------------
/packages/action/src/utils/exec.ts:
--------------------------------------------------------------------------------
1 | import { exec } from "node:child_process";
2 | import { promisify } from "node:util";
3 | import { logger } from "./logger.ts";
4 |
5 | export const execAsync = promisify(exec);
6 |
7 | interface SpawnOptions
8 | extends Omit[1], "throwOnError"> {
9 | /**
10 | * Whether to throw an error if the process exits with a non-zero code
11 | * @default true
12 | */
13 | throwOnError?: boolean;
14 | }
15 |
16 | /**
17 | * Runs a command using Bun's spawn API with better defaults and error handling
18 | */
19 | export async function runCommand(
20 | command: string[],
21 | options: SpawnOptions = {},
22 | ) {
23 | const { throwOnError = true, ...spawnOptions } = options;
24 |
25 | logger.debug(`Running command: ${command.join(" ")}`);
26 |
27 | const proc = Bun.spawn(command, {
28 | stdout: "inherit",
29 | stderr: "inherit",
30 | ...spawnOptions,
31 | });
32 |
33 | const exitCode = await proc.exited;
34 |
35 | if (throwOnError && exitCode !== 0) {
36 | throw new Error(`Process failed with exit code ${exitCode}`);
37 | }
38 |
39 | return exitCode;
40 | }
41 |
--------------------------------------------------------------------------------
/packages/action/src/utils/logger.ts:
--------------------------------------------------------------------------------
1 | export class Logger {
2 | private static instance: Logger;
3 | private isDevMode: boolean;
4 |
5 | private constructor() {
6 | this.isDevMode = process.env.DEV_MODE === "true";
7 | }
8 |
9 | public static getInstance(): Logger {
10 | if (!Logger.instance) {
11 | Logger.instance = new Logger();
12 | }
13 | return Logger.instance;
14 | }
15 |
16 | public info(message: string): void {
17 | console.log(message);
18 | }
19 |
20 | public error(message: string | Error): void {
21 | console.error(message);
22 | }
23 |
24 | public debug(message: string): void {
25 | if (this.isDevMode) {
26 | console.log(`[DEBUG] ${message}`);
27 | }
28 | }
29 |
30 | public warn(message: string): void {
31 | console.warn(message);
32 | }
33 | }
34 |
35 | export const logger = Logger.getInstance();
36 |
--------------------------------------------------------------------------------
/packages/action/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "rootDir": "./src",
7 | "strict": true,
8 | "esModuleInterop": true,
9 | "skipLibCheck": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "allowImportingTsExtensions": true,
12 | "noEmit": true,
13 | "sourceMap": true
14 | },
15 | "include": ["src/**/*"],
16 | "exclude": ["node_modules", "dist"]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/cli/.env-template:
--------------------------------------------------------------------------------
1 | LANGUINE_BASE_URL=http://localhost:3000
2 | LANGUINE_DEBUG=true
--------------------------------------------------------------------------------
/packages/cli/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # languine
2 |
3 | ## 1.0.2
4 |
5 | ### Patch Changes
6 |
7 | - [`bedc557`](https://github.com/midday-ai/languine/commit/bedc5572715809cb4f3e7be575f72967996c06e9) Thanks [@pontusab](https://github.com/pontusab)! - ollama binary detection
8 |
9 | ## 1.0.1
10 |
11 | ### Patch Changes
12 |
13 | - [`a342e69`](https://github.com/midday-ai/languine/commit/a342e69260f58f89d1805814e0a15164ed81c8a6) Thanks [@pontusab](https://github.com/pontusab)! - Update readme
14 |
15 | ## 1.0.0
16 |
17 | ### Major Changes
18 |
19 | - [`b21abd6`](https://github.com/midday-ai/languine/commit/b21abd6481288240ee63125f81fb122c4da4c2fc) Thanks [@pontusab](https://github.com/pontusab)! - Fix diff and yarn install
20 |
--------------------------------------------------------------------------------
/packages/cli/languine.lock:
--------------------------------------------------------------------------------
1 | version: 1
2 | files:
3 | test-diff-files/with-removals.json:
4 | title: 743b9b4364a8145860c6ab8b25112950
5 | description: b5a7adde1af5c87d7fd797b6245c2a39
6 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/auth/logout.ts:
--------------------------------------------------------------------------------
1 | import { intro, outro } from "@clack/prompts";
2 | import { clearSession, loadSession } from "../../utils/session.js";
3 |
4 | export async function logoutCommand() {
5 | intro("Logout from Languine");
6 |
7 | const session = loadSession();
8 |
9 | if (!session) {
10 | outro("You are not logged in.");
11 | return;
12 | }
13 |
14 | clearSession();
15 | outro("Successfully logged out");
16 | }
17 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/auth/whoami.ts:
--------------------------------------------------------------------------------
1 | import chalk from "chalk";
2 | import { client } from "../../utils/api.ts";
3 |
4 | export async function whoamiCommand() {
5 | const user = await client.user.me.query();
6 |
7 | const details = [
8 | ["Name", user.name],
9 | ["Email", user.email],
10 | ];
11 |
12 | console.log();
13 | console.log(chalk.bold("User Details"));
14 | console.log("- ".repeat(20));
15 |
16 | for (const [label, value] of details) {
17 | console.log(`${chalk.dim("•")} ${chalk.bold(label.padEnd(10))} ${value}`);
18 | }
19 |
20 | console.log("- ".repeat(20));
21 | console.log();
22 | }
23 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/overrides/index.ts:
--------------------------------------------------------------------------------
1 | import { select } from "@clack/prompts";
2 | import { isCancel } from "@clack/prompts";
3 | import { pullCommand } from "./pull.js";
4 |
5 | export async function commands(subCommand?: string) {
6 | if (subCommand) {
7 | switch (subCommand) {
8 | case "pull":
9 | await pullCommand();
10 | break;
11 | default:
12 | console.error("Unknown overrides subcommand:", subCommand);
13 | process.exit(1);
14 | }
15 | return;
16 | }
17 |
18 | const overridesCommand = await select({
19 | message: "What would you like to do?",
20 | options: [{ value: "pull", label: "Pull overrides from the server" }],
21 | });
22 |
23 | if (isCancel(overridesCommand)) {
24 | process.exit(0);
25 | }
26 |
27 | await commands(overridesCommand as string);
28 | }
29 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/translations/index.ts:
--------------------------------------------------------------------------------
1 | import { deleteCommand } from "./delete.js";
2 |
3 | export async function commands(subCommand?: string, args: string[] = []) {
4 | switch (subCommand) {
5 | case "delete":
6 | await deleteCommand(args);
7 | break;
8 | default:
9 | throw new Error('Please specify a subcommand: "delete"');
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/cli/src/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import chalk from "chalk";
4 | import dedent from "dedent";
5 | import { runCommands } from "./commands/run.js";
6 | import { loadEnv } from "./utils/env.js";
7 |
8 | const { LANGUINE_BASE_URL } = loadEnv();
9 |
10 | if (!process.argv[2]) {
11 | console.log(
12 | `
13 | ██╗ █████╗ ███╗ ██╗ ██████╗ ██╗ ██╗██╗███╗ ██╗███████╗
14 | ██║ ██╔══██╗████╗ ██║██╔════╝ ██║ ██║██║████╗ ██║██╔════╝
15 | ██║ ███████║██╔██╗ ██║██║ ███╗██║ ██║██║██║██╗ ██║█████╗
16 | ██║ ██╔══██║██║╚██╗██║██║ ██║██║ ██║██║██║╚██╗██║██╔══╝
17 | ███████╗██║ ██║██║ ╚████║╚██████╔╝╚██████╔╝██║██║ ╚████║███████╗
18 | ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝╚══════╝
19 | `,
20 | );
21 |
22 | console.log(
23 | chalk.gray(dedent`
24 | Translate your application with Languine CLI powered by AI.
25 | Website: ${chalk.bold(LANGUINE_BASE_URL)}
26 | `),
27 | );
28 |
29 | console.log();
30 | }
31 |
32 | await runCommands();
33 |
--------------------------------------------------------------------------------
/packages/cli/src/parsers/core/base-parser.ts:
--------------------------------------------------------------------------------
1 | import type { Parser, ParserOptions } from "./types.js";
2 |
3 | export type { ParserOptions };
4 |
5 | export abstract class BaseParser implements Parser {
6 | constructor(protected options: ParserOptions) {}
7 |
8 | abstract parse(input: string): Promise>;
9 |
10 | abstract serialize(
11 | locale: string,
12 | data: Record,
13 | originalData?: string | Record,
14 | ): Promise;
15 | }
16 |
--------------------------------------------------------------------------------
/packages/cli/src/parsers/core/format.ts:
--------------------------------------------------------------------------------
1 | import type { Parser } from "./types.js";
2 |
3 | export function createFormatParser(parser: T): T {
4 | return parser;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/cli/src/parsers/core/types.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export interface ParserOptions {
4 | type: string;
5 | }
6 |
7 | export const parserOptionsSchema = z.object({
8 | type: z.string(),
9 | });
10 |
11 | export interface Parser {
12 | parse(input: string): Promise>;
13 | serialize(
14 | locale: string,
15 | data: Record,
16 | originalData?: string | Record,
17 | sourceData?: string | Record,
18 | ): Promise;
19 | }
20 |
--------------------------------------------------------------------------------
/packages/cli/src/parsers/formats/arb.ts:
--------------------------------------------------------------------------------
1 | import { merge, pickBy } from "rambda";
2 | import { BaseParser } from "../core/base-parser.js";
3 |
4 | export class ArbParser extends BaseParser {
5 | async parse(input: string): Promise> {
6 | try {
7 | const parsed = JSON.parse(input);
8 | return pickBy((_, key) => !key.startsWith("@"), parsed);
9 | } catch (error) {
10 | throw new Error(
11 | `Failed to parse ARB translations: ${error instanceof Error ? error.message : String(error)}`,
12 | );
13 | }
14 | }
15 |
16 | async serialize(
17 | _locale: string,
18 | data: Record,
19 | _originalData?: Record,
20 | ): Promise {
21 | try {
22 | const result = merge({ "@@locale": _locale }, data);
23 | return JSON.stringify(result, null, 2);
24 | } catch (error) {
25 | throw new Error(
26 | `Failed to serialize ARB translations: ${error instanceof Error ? error.message : String(error)}`,
27 | );
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/cli/src/parsers/formats/json.ts:
--------------------------------------------------------------------------------
1 | import { jsonrepair } from "jsonrepair";
2 | import { BaseParser } from "../core/base-parser.js";
3 | import { flatten, unflatten } from "../core/flatten.js";
4 |
5 | export class JsonParser extends BaseParser {
6 | async parse(input: string) {
7 | try {
8 | const parsed = JSON.parse(jsonrepair(input));
9 | if (typeof parsed !== "object" || parsed === null) {
10 | throw new Error("Translation file must contain a JSON object");
11 | }
12 | return flatten(parsed);
13 | } catch (error) {
14 | throw new Error(
15 | `Failed to parse JSON translations: ${error instanceof Error ? error.message : String(error)}`,
16 | );
17 | }
18 | }
19 |
20 | async serialize(
21 | _locale: string,
22 | data: Record,
23 | _originalData?: Record,
24 | ): Promise {
25 | return `${JSON.stringify(unflatten(data), null, 2)}\n`;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/cli/src/parsers/formats/markdown.ts:
--------------------------------------------------------------------------------
1 | import { BaseParser } from "../core/base-parser.ts";
2 |
3 | export class MarkdownParser extends BaseParser {
4 | async parse(input: string) {
5 | return { content: input };
6 | }
7 |
8 | async serialize(
9 | _locale: string,
10 | data: Record,
11 | ): Promise {
12 | return data.content;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/cli/src/parsers/formats/types.ts:
--------------------------------------------------------------------------------
1 | export type XcstringsTranslationEntity = {
2 | localizations?: Record<
3 | string,
4 | {
5 | stringUnit?: { value: string };
6 | variations?: {
7 | plural?: Record;
8 | };
9 | }
10 | >;
11 | };
12 |
13 | export type XcstringsOutput = {
14 | strings: Record<
15 | string,
16 | {
17 | extractionState: string;
18 | localizations: Record<
19 | string,
20 | {
21 | stringUnit: {
22 | state: string;
23 | value: string;
24 | };
25 | }
26 | >;
27 | }
28 | >;
29 | version: string;
30 | sourceLanguage: string;
31 | };
32 |
--------------------------------------------------------------------------------
/packages/cli/src/parsers/formats/yaml.ts:
--------------------------------------------------------------------------------
1 | import YAML from "yaml";
2 | import { BaseParser } from "../core/base-parser.js";
3 | import { flatten, unflatten } from "../core/flatten.js";
4 |
5 | export class YamlParser extends BaseParser {
6 | async parse(input: string) {
7 | try {
8 | const parsed = YAML.parse(input) || {};
9 | if (typeof parsed !== "object" || parsed === null) {
10 | throw new Error("Translation file must contain a YAML object");
11 | }
12 | return flatten(parsed);
13 | } catch (error) {
14 | throw new Error(
15 | `Failed to parse YAML translations: ${error instanceof Error ? error.message : String(error)}`,
16 | );
17 | }
18 | }
19 |
20 | async serialize(
21 | _locale: string,
22 | data: Record,
23 | _originalData?: Record,
24 | ): Promise {
25 | try {
26 | return YAML.stringify(unflatten(data), {
27 | lineWidth: -1,
28 | });
29 | } catch (error) {
30 | throw new Error(
31 | `Failed to serialize YAML translations: ${error instanceof Error ? error.message : String(error)}`,
32 | );
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/env.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { config } from "dotenv";
3 | import { expand } from "dotenv-expand";
4 |
5 | /**
6 | * Loads environment variables from .env file in the specified working directory
7 | * Falls back to default values if no .env file exists
8 | */
9 | export function loadEnv(workingDir: string = process.cwd()) {
10 | // Try to load .env file
11 | const env = config({
12 | path: path.resolve(workingDir, ".env"),
13 | });
14 |
15 | expand(env);
16 |
17 | return {
18 | LANGUINE_DEBUG: env.parsed?.LANGUINE_DEBUG || false,
19 | LANGUINE_BASE_URL: env.parsed?.LANGUINE_BASE_URL || "https://languine.ai",
20 | LANGUINE_PROJECT_ID: env.parsed?.LANGUINE_PROJECT_ID,
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/exec.ts:
--------------------------------------------------------------------------------
1 | import { exec } from "node:child_process";
2 |
3 | export async function execAsync(command: string) {
4 | return await new Promise((resolve, reject) => {
5 | exec(command, (error) => {
6 | if (error) {
7 | reject(error);
8 | } else {
9 | resolve();
10 | }
11 | });
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/packages/cli/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*.ts", "types/**/*.d.ts"],
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "NodeNext",
6 | "moduleResolution": "nodenext",
7 | "allowJs": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "isolatedModules": true,
11 | "esModuleInterop": true,
12 | "outDir": "dist",
13 | "resolveJsonModule": true,
14 | "declaration": true,
15 | "declarationMap": true,
16 | "sourceMap": true,
17 | "noEmit": false,
18 | "customConditions": ["source"],
19 | "allowImportingTsExtensions": true,
20 | "emitDeclarationOnly": true,
21 | "baseUrl": ".",
22 | "paths": {
23 | "@/*": ["src/*"],
24 | "@jobs/*": ["../../apps/web/src/jobs/*"]
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/cli/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig({
4 | dts: true,
5 | format: "esm",
6 | entry: ["src/index.ts", "src/utils/transform.ts"],
7 | });
8 |
--------------------------------------------------------------------------------
/packages/cli/types/xliff.d.ts:
--------------------------------------------------------------------------------
1 | declare module "xliff" {
2 | export interface XliffData {
3 | version: string;
4 | srcLang: string;
5 | trgLang?: string;
6 | ns?: string;
7 | resources: {
8 | [key: string]: {
9 | source: string;
10 | target?: string;
11 | };
12 | };
13 | }
14 |
15 | export function xliff2js(data: string): XliffData;
16 | export function js2xliff(data: XliffData): string;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/react-email/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @languine/react-email
2 |
3 | ## 1.0.1
4 |
5 | ### Patch Changes
6 |
7 | - [`a342e69`](https://github.com/midday-ai/languine/commit/a342e69260f58f89d1805814e0a15164ed81c8a6) Thanks [@pontusab](https://github.com/pontusab)! - Update readme
8 |
9 | ## 1.0.0
10 |
11 | ### Major Changes
12 |
13 | - [#35](https://github.com/midday-ai/languine/pull/35) [`6682eb5`](https://github.com/midday-ai/languine/commit/6682eb5c8166a85b6a400acc9b15dda5f70a0490) Thanks [@pontusab](https://github.com/pontusab)! - Added changelog
14 |
15 | - [`c62d4c0`](https://github.com/midday-ai/languine/commit/c62d4c00d447929b023f571927b429d04fa0e0fd) Thanks [@pontusab](https://github.com/pontusab)! - Changelog
16 |
17 | - [#35](https://github.com/midday-ai/languine/pull/35) [`6682eb5`](https://github.com/midday-ai/languine/commit/6682eb5c8166a85b6a400acc9b15dda5f70a0490) Thanks [@pontusab](https://github.com/pontusab)! - Changelog
18 |
--------------------------------------------------------------------------------
/packages/react-email/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/packages/react-email/image.png
--------------------------------------------------------------------------------
/packages/react-email/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@languine/react-email",
3 | "version": "1.0.1",
4 | "files": [
5 | "dist",
6 | "README.md"
7 | ],
8 | "main": "dist/index.mjs",
9 | "types": "dist/index.d.ts",
10 | "license": "MIT",
11 | "scripts": {
12 | "clean": "rm -rf .turbo node_modules",
13 | "lint": "biome check .",
14 | "format": "biome format --write .",
15 | "typecheck": "tsc --noEmit",
16 | "build": "tsup --clean src/index.tsx"
17 | },
18 | "devDependencies": {
19 | "tsup": "^8.3.5",
20 | "typescript": "^5.7.3"
21 | },
22 | "dependencies": {
23 | "i18n-js": "^4.5.1",
24 | "react-string-replace": "^1.1.1"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/react-email/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { I18n } from "i18n-js";
2 | import { interpolate } from "./interpolate";
3 | import { translations } from "./loader";
4 |
5 | export function setupI18n(locale?: string) {
6 | if (Object.keys(translations).length === 0) {
7 | throw new Error(
8 | "No translation files found in locales directory, make sure it's in the root of the package",
9 | );
10 | }
11 |
12 | const i18n = new I18n(translations);
13 |
14 | // Set locale to first available locale if no locale is provided
15 | i18n.locale = locale || Object.keys(translations).at(0) || "en";
16 | i18n.enableFallback = true;
17 | // @ts-ignore
18 | i18n.interpolate = interpolate;
19 |
20 | return i18n;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/react-email/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*.ts", "src/**/*.tsx"],
3 | "compilerOptions": {
4 | "target": "es2020",
5 | "module": "es2020",
6 | "moduleResolution": "node",
7 | "allowJs": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "isolatedModules": true,
11 | "esModuleInterop": true,
12 | "outDir": "dist",
13 | "resolveJsonModule": true,
14 | "declaration": true,
15 | "declarationMap": true,
16 | "sourceMap": true,
17 | "noEmit": false,
18 | "jsx": "react-jsx"
19 | }
20 | }
--------------------------------------------------------------------------------
/packages/react-email/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig({
4 | dts: true,
5 | format: "esm",
6 | entry: ["src/index.ts"],
7 | });
8 |
--------------------------------------------------------------------------------
/packages/sdk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@languine/sdk",
3 | "type": "module",
4 | "license": "MIT",
5 | "version": "1.0.1",
6 | "description": "Languine SDK",
7 | "main": "dist/index.js",
8 | "types": "dist/index.d.ts",
9 | "scripts": {
10 | "clean": "rm -rf .turbo node_modules dist",
11 | "lint": "biome check .",
12 | "format": "biome format --write .",
13 | "typecheck": "tsc --noEmit",
14 | "build": "tsup --clean",
15 | "test": "bun test",
16 | "publish:canary": "npm version --prerelease --preid=canary && npm publish --prerelease --preid=canary --tag=canary --access public"
17 | },
18 | "files": [
19 | "dist"
20 | ],
21 | "devDependencies": {
22 | "tsup": "^8.2.0"
23 | }
24 | }
--------------------------------------------------------------------------------
/packages/sdk/src/types.ts:
--------------------------------------------------------------------------------
1 | export const FORMAT_ENUM = [
2 | "string",
3 | "json",
4 | "yaml",
5 | "properties",
6 | "android",
7 | "xcode-strings",
8 | "xcode-stringsdict",
9 | "xcode-xcstrings",
10 | "md",
11 | "mdx",
12 | "html",
13 | "js",
14 | "ts",
15 | "po",
16 | "xliff",
17 | "csv",
18 | "xml",
19 | "arb",
20 | ] as const;
21 |
22 | export type Format = (typeof FORMAT_ENUM)[number];
23 |
24 | export interface TranslateParams {
25 | projectId: string;
26 | sourceLocale: string;
27 | targetLocale: string;
28 | format?: Format;
29 | sourceText: string;
30 | cache?: boolean;
31 | }
32 |
33 | export interface TranslateResponse {
34 | success: boolean;
35 | translatedText: string;
36 | cached: boolean;
37 | }
38 |
--------------------------------------------------------------------------------
/packages/sdk/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*.ts", "types/**/*.d.ts"],
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "NodeNext",
6 | "allowJs": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "isolatedModules": true,
10 | "esModuleInterop": true,
11 | "outDir": "dist",
12 | "resolveJsonModule": true,
13 | "declaration": true,
14 | "declarationMap": true,
15 | "sourceMap": true,
16 | "noEmit": false,
17 | "customConditions": ["source"],
18 | "allowImportingTsExtensions": false,
19 | "emitDeclarationOnly": true,
20 | "baseUrl": "."
21 | }
22 | }
--------------------------------------------------------------------------------
/packages/sdk/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig({
4 | dts: true,
5 | format: "esm",
6 | entry: ["src/index.ts"],
7 | });
8 |
--------------------------------------------------------------------------------
/packages/supabase/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@languine/supabase",
3 | "version": "1.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "@supabase/ssr": "^0.5.2",
7 | "@supabase/supabase-js": "^2.48.1",
8 | "drizzle-orm": "^0.38.4"
9 | },
10 | "exports": {
11 | "./session": "./src/auth/session.ts",
12 | "./client": "./src/client/client.ts",
13 | "./server": "./src/client/server.ts",
14 | "./middleware": "./src/client/middleware.ts",
15 | "./utils": "./src/client/utils.ts"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/supabase/src/auth/client.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/languine-ai/languine/ba04eb9a68e63f78c3a9cb6da3d1370fa4ccfc27/packages/supabase/src/auth/client.ts
--------------------------------------------------------------------------------
/packages/supabase/src/auth/session.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from "../client/server";
2 |
3 | export async function getSession() {
4 | const supabase = await createClient();
5 |
6 | return supabase.auth.getSession();
7 | }
8 |
--------------------------------------------------------------------------------
/packages/supabase/src/client/client.ts:
--------------------------------------------------------------------------------
1 | import { createBrowserClient } from "@supabase/ssr";
2 |
3 | export function createClient() {
4 | return createBrowserClient(
5 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
6 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/packages/supabase/src/client/server.ts:
--------------------------------------------------------------------------------
1 | import { createServerClient } from "@supabase/ssr";
2 | import { cookies } from "next/headers";
3 |
4 | export async function createClient() {
5 | const cookieStore = await cookies();
6 |
7 | return createServerClient(
8 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
9 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
10 | {
11 | cookies: {
12 | getAll() {
13 | return cookieStore.getAll();
14 | },
15 | setAll(cookiesToSet) {
16 | try {
17 | for (const { name, value, options } of cookiesToSet) {
18 | cookieStore.set(name, value, options);
19 | }
20 | } catch {
21 | // The `setAll` method was called from a Server Component.
22 | // This can be ignored if you have middleware refreshing
23 | // user sessions.
24 | }
25 | },
26 | },
27 | global: {
28 | headers: {
29 | "sb-lb-routing-mode": "alpha-all-services",
30 | },
31 | },
32 | },
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/packages/supabase/src/client/utils.ts:
--------------------------------------------------------------------------------
1 | import type { NextResponse } from "next/server";
2 |
3 | export const SKIP_SESSION_REFRESH_COOKIE = "skip-session-refresh";
4 |
5 | export function setSkipSessionRefreshCookie(
6 | response: NextResponse,
7 | value: boolean,
8 | ) {
9 | response.cookies.set({
10 | name: SKIP_SESSION_REFRESH_COOKIE,
11 | value: value ? "true" : "",
12 | path: "/",
13 | httpOnly: true,
14 | secure: process.env.NODE_ENV === "production",
15 | maxAge: value ? 30 * 60 : 0,
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/packages/supabase/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "lib": ["esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "incremental": true,
16 | "baseUrl": ".",
17 | "paths": {
18 | "@/*": ["./src/*"]
19 | }
20 | },
21 | "include": ["**/*.ts"],
22 | "exclude": ["node_modules"]
23 | }
24 |
25 |
--------------------------------------------------------------------------------