├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── next.svg ├── test2.jpg └── vercel.svg ├── src ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ ├── Buttons │ │ └── IconButton.jsx │ ├── Inputs │ │ ├── FileUpload.jsx │ │ ├── LanguageSelector.jsx │ │ ├── LinkPaste.jsx │ │ └── TextArea.jsx │ ├── SpeechRecognition │ │ └── SpeechRecognition.jsx │ ├── SvgDecorations.tsx │ └── categoryLinks.tsx ├── hooks │ └── useTranslate.jsx └── utils │ └── rtfToText.js ├── tailwind.config.ts ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | .env 9 | /.env 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 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 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Project Banner 5 | 6 | 7 |
8 | 9 |
10 | typescript 11 | nextdotjs 12 | tailwindcss 13 |
14 | 15 |

LinguaSpeak

16 | 17 |
18 | Build this project step by step with our detailed tutorial on Your YouTube Channel. Join the community! 19 |
20 |
21 | 22 | ## 📋 Table of Contents 23 | 24 | 1. 🤖 [Introduction](#introduction) 25 | 2. ⚙️ [Tech Stack](#tech-stack) 26 | 3. 🔋 [Features](#features) 27 | 4. 🤸 [Quick Start](#quick-start) 28 | 5. 🕸️ [Assets & Code](#snippets) 29 | 6. 🚀 [More](#more) 30 | 31 | ## 🚨 Tutorial 32 | 33 | This repository contains the code corresponding to an in-depth tutorial available on our YouTube channel, Code with Albert. 34 | 35 | If you prefer visual learning, this is the perfect resource for you. Follow our tutorial to learn how to build projects like these step-by-step in a beginner-friendly manner! 36 | 37 | ## 🤖 Introduction 38 | 39 | Built with the latest Next.js and TypeScript, LinguaSpeak is an advanced voice translation tool. It allows users to speak into the microphone, translate the spoken text to another language, and playback the translation. This project is perfect for those looking to learn how to integrate speech recognition and translation APIs into a Next.js application. 40 | 41 | If you're getting started and need assistance or face any bugs, join our active Discord community. It's a place where people help each other out. 42 | 43 | 44 | 45 | ## ⚙️ Tech Stack 46 | 47 | - Next.js 48 | - TypeScript 49 | - OpenAI API 50 | - React Speech Recognition 51 | - Tailwind CSS 52 | 53 | ## 🔋 Features 54 | 55 | 👉 **Speech Recognition**: Converts spoken words into text using the Web Speech API. 56 | 57 | 👉 **Text Translation**: Translates the recognized text into a selected target language using OpenAI's GPT-4 model. 58 | 59 | 👉 **Audio Playback**: Converts translated text back into speech using the Web Speech API's speech synthesis. 60 | 61 | 👉 **File Upload**: Reads and translates text from uploaded files, including RTF to plain text conversion. 62 | 63 | 👉 **Link Content Fetching**: Fetches and translates text content from provided URLs. 64 | 65 | 👉 **Language Selection**: Allows users to select target languages for translation. 66 | 67 | 👉 **Copy to Clipboard**: Copies the translated text to the clipboard. 68 | 69 | 👉 **Like, Dislike, and Favorite**: Provides user interaction features for translations. 70 | 71 | 👉 **Responsive Design**: Ensures a seamless experience across different devices. 72 | 73 | ## 🤸 Quick Start 74 | 75 | Follow these steps to set up the project locally on your machine. 76 | 77 | **Prerequisites** 78 | 79 | Make sure you have the following installed on your machine: 80 | 81 | - [Git](https://git-scm.com/) 82 | - [Node.js](https://nodejs.org/en) 83 | - [npm](https://www.npmjs.com/) (Node Package Manager) 84 | 85 | **Cloning the Repository** 86 | 87 | ```bash 88 | git clone https://github.com/mendsalbert/lingua-speak.git 89 | cd linguaspeak 90 | ``` 91 | 92 | **Installation** 93 | 94 | Install the project dependencies using npm: 95 | 96 | ```bash 97 | npm install 98 | ``` 99 | 100 | **Set Up Environment Variables** 101 | 102 | Create a new file named `.env` in the root of your project and add the following content: 103 | 104 | ```env 105 | NEXT_PUBLIC_API_KEY=your-openai-api-key 106 | ``` 107 | 108 | Replace the placeholder values with your actual OpenAI credentials. You can obtain these credentials by signing up on the [OpenAI website](https://openai.com/). 109 | 110 | **Running the Project** 111 | 112 | ```bash 113 | npm run dev 114 | ``` 115 | 116 | Open [http://localhost:3000](http://localhost:3000) in your browser to view the project. 117 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lingua-speak", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@babel/plugin-transform-runtime": "^7.24.7", 13 | "@tabler/icons-react": "^3.8.0", 14 | "@types/react-speech-recognition": "^3.9.5", 15 | "clsx": "^2.1.1", 16 | "framer-motion": "^11.2.12", 17 | "mini-svg-data-uri": "^1.4.4", 18 | "next": "14.2.4", 19 | "openai": "^4.52.3", 20 | "preline": "^2.3.0", 21 | "react": "^18", 22 | "react-dom": "^18", 23 | "react-speech-recognition": "^3.10.0", 24 | "rtf-parser": "^1.3.3", 25 | "tailwind-merge": "^2.3.0" 26 | }, 27 | "devDependencies": { 28 | "@types/node": "^20", 29 | "@types/react": "^18", 30 | "@types/react-dom": "^18", 31 | "eslint": "^8", 32 | "eslint-config-next": "14.2.4", 33 | "postcss": "^8", 34 | "tailwindcss": "^3.4.1", 35 | "typescript": "^5" 36 | }, 37 | "browserslist": [ 38 | "last 2 Chrome versions" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/test2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendsalbert/lingua-speak/817e58dca91aa8259f8497bc38e05340b06872e6/public/test2.jpg -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendsalbert/lingua-speak/817e58dca91aa8259f8497bc38e05340b06872e6/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | 29 | @layer utilities { 30 | .text-balance { 31 | text-wrap: balance; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | 20 | 21 | {children} 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import "regenerator-runtime/runtime"; 3 | import React, { useState, ChangeEvent } from "react"; 4 | import { 5 | IconCopy, 6 | IconStar, 7 | IconThumbDown, 8 | IconThumbUp, 9 | IconVolume, 10 | } from "@tabler/icons-react"; 11 | import SpeechRecognitionComponent from "@/components/SpeechRecognition/SpeechRecognition"; 12 | import TextArea from "@/components/Inputs/TextArea"; 13 | import FileUpload from "@/components/Inputs/FileUpload"; 14 | import LinkPaste from "@/components/Inputs/LinkPaste"; 15 | import LanguageSelector from "@/components/Inputs/LanguageSelector"; 16 | import useTranslate from "@/hooks/useTranslate"; 17 | import { rtfToText } from "@/utils/rtfToText"; 18 | 19 | import SvgDecorations from "@/components/SvgDecorations"; 20 | import CategoryLinks from "@/components/categoryLinks"; 21 | 22 | const Home: React.FC = () => { 23 | const [sourceText, setSourceText] = useState(""); 24 | const [copied, setCopied] = useState(false); 25 | const [favorite, setFavorite] = useState(false); 26 | const [languages] = useState([ 27 | "English", 28 | "Spanish", 29 | "French", 30 | "German", 31 | "Chinese", 32 | ]); 33 | const [selectedLanguage, setSelectedLanguage] = useState("Spanish"); 34 | 35 | const targetText = useTranslate(sourceText, selectedLanguage); 36 | 37 | const handleFileUpload = (e: ChangeEvent) => { 38 | const file = e.target.files?.[0]; 39 | if (file) { 40 | const reader = new FileReader(); 41 | reader.onload = () => { 42 | const rtfContent = reader.result as string; 43 | const text = rtfToText(rtfContent); 44 | setSourceText(text); 45 | }; 46 | reader.readAsText(file); 47 | } 48 | }; 49 | 50 | const handleLinkPaste = async (e: ChangeEvent) => { 51 | const link = e.target.value; 52 | try { 53 | const response = await fetch(link); 54 | const data = await response.text(); 55 | setSourceText(data); 56 | } catch (error) { 57 | console.error("Error fetching link content:", error); 58 | } 59 | }; 60 | 61 | const handleCopyToClipboard = () => { 62 | navigator.clipboard.writeText(targetText); 63 | setCopied(true); 64 | setTimeout(() => setCopied(false), 2000); 65 | }; 66 | 67 | const handleLike = () => { 68 | // Implement like logic 69 | }; 70 | 71 | const handleDislike = () => { 72 | // Implement dislike logic 73 | }; 74 | 75 | const handleFavorite = () => { 76 | setFavorite(!favorite); 77 | if (!favorite) { 78 | localStorage.setItem("favoriteTranslation", targetText); 79 | } else { 80 | localStorage.removeItem("favoriteTranslation"); 81 | } 82 | }; 83 | 84 | const handleAudioPlayback = (text: string) => { 85 | const utterance = new SpeechSynthesisUtterance(text); 86 | window.speechSynthesis.speak(utterance); 87 | }; 88 | 89 | return ( 90 |
91 |
92 | 93 |
94 |
95 |
96 |

97 | LinguaSpeak 98 |

99 | 100 |

101 | LinguaSpeak: Bridging Voices, Connecting Worlds. 102 |

103 | 104 |
105 |
106 |
107 |