diff --git a/.changeset/fair-shirts-work.md b/.changeset/fair-shirts-work.md new file mode 100644 index 000000000..7148b76b3 --- /dev/null +++ b/.changeset/fair-shirts-work.md @@ -0,0 +1,17 @@ +--- +"@compiler/demo-next": patch +"@replexica/integration-directus": patch +"replexica": patch +"@replexica/sdk": patch +"lingo.dev": patch +"@lingo.dev/_compiler": patch +"@lingo.dev/_locales": patch +"@lingo.dev/_logging": patch +"@lingo.dev/compiler": patch +"@lingo.dev/_react": patch +"@lingo.dev/_sdk": patch +"@lingo.dev/_spec": patch +"docs": patch +--- + +added missing changeset diff --git a/community/auto-localized-notes/.gitignore b/community/auto-localized-notes/.gitignore new file mode 100644 index 000000000..5ef6a5207 --- /dev/null +++ b/community/auto-localized-notes/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/community/auto-localized-notes/README.md b/community/auto-localized-notes/README.md new file mode 100644 index 000000000..894e991c1 --- /dev/null +++ b/community/auto-localized-notes/README.md @@ -0,0 +1,78 @@ +# Auto-Localized Notes + +A demo Next.js application showcasing **lingo.dev SDK** usage for automatic language detection and translation. +Users can write notes in any language, and the app automatically detects the source language and translates the note into a target language. + +This project is intentionally minimal to focus on demonstrating **SDK integration, server/client separation, and real-world usage**, rather than application complexity. + +--- + +## What this project does + +- Accepts user input in **any language** +- Detects the input language automatically +- Translates the text into a selected target language +- Displays both: + - Original text + - Translated text +- Allows searching notes by translated content + +The lingo.dev SDK is used **server-side only** via Next.js Server Actions to ensure correct handling of Node-only dependencies and API keys. + +--- + +## Tech Stack + +- **Next.js (App Router)** +- **TypeScript** +- **Tailwind CSS** +- **lingo.dev SDK** + +--- + +## Prerequisites + +Before running this project locally, ensure you have: + +- **Node.js** `>= 18` +- **pnpm / npm / yarn** +- A **lingo.dev API key** + +### Environment variables + +Create a `.env.local` file in the project root: + +```env +LINGODOTDEV_API_KEY=your_api_key_here +``` + +--- + +## How to run locally + +1. Clone the repository + +``` +git clone +cd auto-localized-notes +``` + +2. Install dependencies + +``` +npm install +# or +pnpm install +``` + +3. Start the development server + +``` +npm run dev +``` + +4. Open in browser + +``` +http://localhost:3000 +``` diff --git a/community/auto-localized-notes/app/actions/translate.ts b/community/auto-localized-notes/app/actions/translate.ts new file mode 100644 index 000000000..5a3065148 --- /dev/null +++ b/community/auto-localized-notes/app/actions/translate.ts @@ -0,0 +1,36 @@ +"use server"; + +import { LingoDotDevEngine } from "lingo.dev/sdk"; + +const lingoDotDev = new LingoDotDevEngine({ + apiKey: process.env.LINGODOTDEV_API_KEY, +}); + +export async function translateText( + text: string, + fromLang: string, + toLang: string, +) { + try { + const result = await lingoDotDev.localizeText(text, { + sourceLocale: fromLang, + targetLocale: toLang, + }); + + return result; + } catch (error) { + console.error(error); + return "error translating the text"; + } +} + +export async function recognizeText(text: string) { + try { + const detectedLang = await lingoDotDev.recognizeLocale(text); + + return detectedLang; + } catch (error) { + console.error(error); + return "error detecting the language"; + } +} diff --git a/community/auto-localized-notes/app/favicon.ico b/community/auto-localized-notes/app/favicon.ico new file mode 100644 index 000000000..718d6fea4 Binary files /dev/null and b/community/auto-localized-notes/app/favicon.ico differ diff --git a/community/auto-localized-notes/app/globals.css b/community/auto-localized-notes/app/globals.css new file mode 100644 index 000000000..a2dc41ece --- /dev/null +++ b/community/auto-localized-notes/app/globals.css @@ -0,0 +1,26 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/community/auto-localized-notes/app/layout.tsx b/community/auto-localized-notes/app/layout.tsx new file mode 100644 index 000000000..f7fa87eb8 --- /dev/null +++ b/community/auto-localized-notes/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/community/auto-localized-notes/app/page.tsx b/community/auto-localized-notes/app/page.tsx new file mode 100644 index 000000000..8379ad920 --- /dev/null +++ b/community/auto-localized-notes/app/page.tsx @@ -0,0 +1,159 @@ +"use client"; + +import { useState, useTransition } from "react"; +import { recognizeText, translateText } from "./actions/translate"; + +type Note = { + id: string; + original: string; + translated: string; + detectedLang: string; +}; + +export default function HomePage() { + const [text, setText] = useState(""); + const [query, setQuery] = useState(""); + const [searchQuery, setSearchQuery] = useState(""); + const [lang, setLang] = useState("en"); + const [notes, setNotes] = useState([]); + const [isPending, startTransition] = useTransition(); + + const languages = ["en", "hi", "fr", "es"]; + + const handleChange = (e: any) => { + setLang(e.target.value); + }; + + const handleSubmit = () => { + if (!text.trim()) return; + + startTransition(async () => { + const detectedLang = await recognizeText(text); + const result = await translateText(text, detectedLang, lang); + + setNotes((prev) => [ + { + id: crypto.randomUUID(), + original: text, + translated: result, + detectedLang: detectedLang, + }, + ...prev, + ]); + + setText(""); + }); + }; + + const filteredNotes = + query === "" + ? notes + : notes.filter( + (note) => + note.translated.toLowerCase().includes(query.toLowerCase()) || + note.original.toLowerCase().includes(query.toLowerCase()), + ); + + const querySearch = () => { + setQuery(searchQuery.trim()); + }; + + return ( +
+
+ {/* Header */} +
+

+ Auto-Localized Notes +

+

+ Write in any language. Read in your own. +

+
+ + {/* Search */} +
+ +
+ setSearchQuery(e.target.value)} + /> + +
+
+ + {/* Input */} +
+ + +