Add translation support for website
All checks were successful
Queue Release Build / prepare (push) Successful in 1m20s
Deploy Web Apps / deploy (push) Successful in 17m37s
Queue Release Build / build-windows (push) Successful in 39m8s
Queue Release Build / build-linux (push) Successful in 1h3m53s
Queue Release Build / finalize (push) Successful in 5m43s
All checks were successful
Queue Release Build / prepare (push) Successful in 1m20s
Deploy Web Apps / deploy (push) Successful in 17m37s
Queue Release Build / build-windows (push) Successful in 39m8s
Queue Release Build / build-linux (push) Successful in 1h3m53s
Queue Release Build / finalize (push) Successful in 5m43s
This commit is contained in:
148
website/package-lock.json
generated
148
website/package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"@angular/platform-server": "^19.2.0",
|
||||
"@angular/router": "^19.2.0",
|
||||
"@angular/ssr": "^19.2.21",
|
||||
"@ngx-translate/core": "^17.0.0",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tsparticles/angular": "^3.0.0",
|
||||
"@tsparticles/engine": "^3.9.1",
|
||||
@@ -670,6 +671,87 @@
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/localize": {
|
||||
"version": "19.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-19.2.19.tgz",
|
||||
"integrity": "sha512-FlnungTK9pNDi283j0mhuALRzgVj56vfEH//dM8/9CsNtfpGoBnKBOZl/aN//ShilAnjP1UFN40kYVRxgI1kjg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.26.9",
|
||||
"@types/babel__core": "7.20.5",
|
||||
"fast-glob": "3.3.3",
|
||||
"yargs": "^17.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"localize-extract": "tools/bundles/src/extract/cli.js",
|
||||
"localize-migrate": "tools/bundles/src/migrate/cli.js",
|
||||
"localize-translate": "tools/bundles/src/translate/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "19.2.19",
|
||||
"@angular/compiler-cli": "19.2.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/localize/node_modules/@babel/core": {
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz",
|
||||
"integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/generator": "^7.26.9",
|
||||
"@babel/helper-compilation-targets": "^7.26.5",
|
||||
"@babel/helper-module-transforms": "^7.26.0",
|
||||
"@babel/helpers": "^7.26.9",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/traverse": "^7.26.9",
|
||||
"@babel/types": "^7.26.9",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
"json5": "^2.2.3",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/babel"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/localize/node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@angular/localize/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser": {
|
||||
"version": "19.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.19.tgz",
|
||||
@@ -4403,6 +4485,19 @@
|
||||
"webpack": "^5.54.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ngx-translate/core": {
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-17.0.0.tgz",
|
||||
"integrity": "sha512-Rft2D5ns2pq4orLZjEtx1uhNuEBerUdpFUG1IcqtGuipj6SavgB8SkxtNQALNDA+EVlvsNCCjC2ewZVtUeN6rg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=16",
|
||||
"@angular/core": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -6083,6 +6178,59 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.20.7",
|
||||
"@babel/types": "^7.20.7",
|
||||
"@types/babel__generator": "*",
|
||||
"@types/babel__template": "*",
|
||||
"@types/babel__traverse": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__generator": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
|
||||
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__template": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
|
||||
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.1.0",
|
||||
"@babel/types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__traverse": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
|
||||
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@angular/platform-server": "^19.2.0",
|
||||
"@angular/router": "^19.2.0",
|
||||
"@angular/ssr": "^19.2.21",
|
||||
"@ngx-translate/core": "^17.0.0",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tsparticles/angular": "^3.0.0",
|
||||
"@tsparticles/engine": "^3.9.1",
|
||||
|
||||
353
website/public/i18n/en.json
Normal file
353
website/public/i18n/en.json
Normal file
@@ -0,0 +1,353 @@
|
||||
{
|
||||
"common": {
|
||||
"brand": "Toju",
|
||||
"os": {
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux",
|
||||
"linuxDebian": "Linux (deb)",
|
||||
"archive": "Archive",
|
||||
"web": "Web",
|
||||
"other": "Other"
|
||||
},
|
||||
"actions": {
|
||||
"downloadBrand": "Download Toju",
|
||||
"downloadFor": "Download for {{os}}",
|
||||
"openInBrowser": "Open in Browser",
|
||||
"tryInBrowser": "Try in Browser",
|
||||
"useWebVersion": "Use Web Version",
|
||||
"openWebVersion": "Open web version",
|
||||
"goToDownloads": "Go to downloads",
|
||||
"learnHowItWorks": "Learn how it works",
|
||||
"viewSourceCode": "View source code",
|
||||
"buyUsCoffee": "Buy us a coffee"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"header": {
|
||||
"homeAriaLabel": "Toju home",
|
||||
"beta": "Beta",
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"whatIsToju": "What is Toju?",
|
||||
"downloads": "Downloads",
|
||||
"philosophy": "Our Philosophy"
|
||||
},
|
||||
"supportUs": "Support Us",
|
||||
"useWebVersion": "Use Web Version",
|
||||
"toggleMenu": "Toggle menu"
|
||||
},
|
||||
"footer": {
|
||||
"description": "Free, open-source, peer-to-peer communication. Built by people who believe privacy is a right, not a premium feature.",
|
||||
"sections": {
|
||||
"product": "Product",
|
||||
"community": "Community",
|
||||
"values": "Values"
|
||||
},
|
||||
"links": {
|
||||
"downloads": "Downloads",
|
||||
"webVersion": "Web Version",
|
||||
"whatIsToju": "What is Toju?",
|
||||
"imageGallery": "Image Gallery",
|
||||
"sourceCode": "Source Code",
|
||||
"github": "GitHub",
|
||||
"supportUs": "Support Us",
|
||||
"ourPhilosophy": "Our Philosophy"
|
||||
},
|
||||
"values": {
|
||||
"freeForever": "100% Free Forever",
|
||||
"openSource": "Open Source"
|
||||
},
|
||||
"copyright": "© {{year}} Myxelium. Toju is open-source software.",
|
||||
"viewSourceOnGitea": "View source code on Gitea",
|
||||
"viewProjectOnGitHub": "View the project on GitHub"
|
||||
},
|
||||
"adSlot": {
|
||||
"label": "Advertisement"
|
||||
}
|
||||
},
|
||||
"pages": {
|
||||
"home": {
|
||||
"seo": {
|
||||
"title": "Free Peer-to-Peer Voice, Video & Chat",
|
||||
"description": "Toju is a free, open-source, peer-to-peer communication app. Crystal-clear voice calls, unlimited screen sharing, no file size limits, complete privacy."
|
||||
},
|
||||
"hero": {
|
||||
"badge": "Currently in Beta - Free & Open Source",
|
||||
"titleLine1": "Talk freely.",
|
||||
"titleLine2": "Own your voice.",
|
||||
"description": "Crystal-clear voice calls, unlimited screen sharing, and file transfers with no size limits. Peer-to-peer. Private. Completely free.",
|
||||
"version": "Version {{version}}",
|
||||
"allPlatforms": "All platforms"
|
||||
},
|
||||
"features": {
|
||||
"titleLine1": "Everything you need,",
|
||||
"titleLine2": "nothing you don't.",
|
||||
"description": "No bloat. No paywalls. Just the tools to connect with the people who matter.",
|
||||
"items": {
|
||||
"voiceCalls": {
|
||||
"title": "HD Voice Calls",
|
||||
"description": "Crystal-clear audio with built-in noise reduction. Hear every word, not the background. No quality compromises, ever."
|
||||
},
|
||||
"screenSharing": {
|
||||
"title": "Screen Sharing",
|
||||
"description": "Share your screen at full resolution. No time limits, no quality downgrades. Perfect for pair programming, presentations, or showing your epic gameplay."
|
||||
},
|
||||
"fileSharing": {
|
||||
"title": "Unlimited File Sharing",
|
||||
"description": "Send files of any size directly to your friends. No upload limits, no compression. Your files go straight from you to them."
|
||||
},
|
||||
"privacy": {
|
||||
"title": "True Privacy",
|
||||
"description": "Peer-to-peer means your data goes directly between you and your friends. No servers storing your conversations. Your business is your business."
|
||||
},
|
||||
"openSource": {
|
||||
"title": "Open Source",
|
||||
"description": "Every line of code is public. Audit it, modify it, host your own signal server. Full transparency - nothing is hidden."
|
||||
},
|
||||
"free": {
|
||||
"title": "Completely Free",
|
||||
"description": "No premium tiers. No paywalls. No \"starter plans\". Every feature is available to everyone, always. Made with love, not profit margins."
|
||||
}
|
||||
}
|
||||
},
|
||||
"gaming": {
|
||||
"badge": "Built for Gamers",
|
||||
"titleLine1": "Your perfect",
|
||||
"titleLine2": "gaming companion.",
|
||||
"description": "Ultra-low latency voice chat that doesn't eat your bandwidth. Share your screen without frame drops. Send clips and files instantly. All while keeping your CPU free for what matters - winning.",
|
||||
"bullets": {
|
||||
"lowLatency": "Low-latency peer-to-peer voice - no relay servers in the way",
|
||||
"noiseSuppression": "AI-powered noise suppression - keyboard clatter stays out",
|
||||
"screenShare": "Full-resolution screen sharing at high FPS",
|
||||
"fileTransfers": "Send replays and screenshots with no file size limit"
|
||||
},
|
||||
"imageAlt": "Toju gaming screen sharing preview",
|
||||
"caption": "Game on. No limits."
|
||||
},
|
||||
"selfHostable": {
|
||||
"badge": "Self-Hostable",
|
||||
"titleLine1": "Your infrastructure,",
|
||||
"titleLine2": "your rules.",
|
||||
"description": "Toju uses a lightweight coordination server to help peers find each other - that's it. Your actual conversations never touch a server. Want even more control? Run your own coordination server in minutes. Full independence, zero compromises."
|
||||
},
|
||||
"cta": {
|
||||
"title": "Ready to take back your conversations?",
|
||||
"description": "Join thousands choosing privacy, freedom, and real connection."
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"seo": {
|
||||
"title": "Download Toju",
|
||||
"description": "Download Toju for Windows, Linux, or use the web version. Free peer-to-peer voice chat, screen sharing, and file transfers."
|
||||
},
|
||||
"hero": {
|
||||
"titlePrefix": "Download",
|
||||
"description": "Available for Windows, Linux, and in your browser. Always free, always the full experience."
|
||||
},
|
||||
"recommended": {
|
||||
"badge": "Recommended for you",
|
||||
"title": "Toju for {{os}}",
|
||||
"version": "Version {{version}}",
|
||||
"webVersionPrefix": "Or",
|
||||
"webVersionLink": "use the web version",
|
||||
"webVersionSuffix": "- no download required."
|
||||
},
|
||||
"allPlatforms": {
|
||||
"title": "All platforms",
|
||||
"assetIconAlt": "{{os}} icon"
|
||||
},
|
||||
"previousReleases": {
|
||||
"title": "Previous Releases",
|
||||
"fileCount": "{{count}} files"
|
||||
},
|
||||
"loading": "Fetching releases...",
|
||||
"rss": {
|
||||
"prefix": "Stay updated with our",
|
||||
"link": "RSS feed"
|
||||
}
|
||||
},
|
||||
"gallery": {
|
||||
"seo": {
|
||||
"title": "Toju Image Gallery",
|
||||
"description": "Browse screenshots of Toju and explore the interface for chat, file sharing, voice, and screen sharing."
|
||||
},
|
||||
"hero": {
|
||||
"badge": "Image Gallery",
|
||||
"titlePrefix": "A closer look at",
|
||||
"description": "Explore screenshots of the app experience, from voice chat and media sharing to servers, rooms, and full-screen collaboration."
|
||||
},
|
||||
"featured": {
|
||||
"imageAlt": "Toju main application screenshot",
|
||||
"label": "Featured",
|
||||
"title": "The full Toju workspace",
|
||||
"description": "See the main interface where rooms, messages, presence, and media all come together in one focused layout."
|
||||
},
|
||||
"items": {
|
||||
"mainChatView": {
|
||||
"title": "Main chat view",
|
||||
"description": "The core Toju experience with channels, messages, and direct communication tools."
|
||||
},
|
||||
"gamingScreenShare": {
|
||||
"title": "Gaming screen share",
|
||||
"description": "Share gameplay, guides, and live moments with smooth full-resolution screen sharing."
|
||||
},
|
||||
"serverOverview": {
|
||||
"title": "Server overview",
|
||||
"description": "Navigate servers and rooms with a layout designed for clarity and speed."
|
||||
},
|
||||
"musicAndVoice": {
|
||||
"title": "Music and voice",
|
||||
"description": "Stay in sync with voice and media features in a focused, low-friction interface."
|
||||
},
|
||||
"videoSharing": {
|
||||
"title": "Video sharing",
|
||||
"description": "Preview and share visual content directly with your friends and communities."
|
||||
},
|
||||
"fileTransfers": {
|
||||
"title": "File transfers",
|
||||
"description": "Move files quickly without artificial size limits or unnecessary hoops."
|
||||
},
|
||||
"richMediaChat": {
|
||||
"title": "Rich media chat",
|
||||
"description": "Conversations stay lively with visual media support built right in."
|
||||
}
|
||||
},
|
||||
"cta": {
|
||||
"title": "Want to see it in action?",
|
||||
"description": "Download Toju or jump into the browser experience and explore the interface yourself."
|
||||
}
|
||||
},
|
||||
"philosophy": {
|
||||
"seo": {
|
||||
"title": "Our Philosophy - Why We Build Toju",
|
||||
"description": "Toju exists because privacy is a right, not a premium feature. No paywalls, no data harvesting, no predatory pricing. Learn why we build free, open-source communication tools."
|
||||
},
|
||||
"hero": {
|
||||
"badge": "Our Manifesto",
|
||||
"titlePrefix": "Why we",
|
||||
"titleHighlight": "build",
|
||||
"description": "A letter from the people behind the project."
|
||||
},
|
||||
"sections": {
|
||||
"ownership": {
|
||||
"title": "We Lost Something Important",
|
||||
"paragraph1": "Over the past two decades, something fundamental shifted. Our conversations, our memories, our connections - they stopped belonging to us. They live on servers we don't control, inside apps that treat our personal lives as data to be harvested, analyzed, and sold to the highest bidder.",
|
||||
"paragraph2": "We gave up ownership of our digital lives so gradually that most of us didn't even notice. A \"free\" app here, a convenient service there - each one taking a little more of our privacy in exchange for convenience. Toju exists because we believe it's time to take it back."
|
||||
},
|
||||
"paywalls": {
|
||||
"title": "No Paywalls. No Premium Tiers. Ever.",
|
||||
"paragraph1": "You know the playbook: launch a free product, build a user base, then start locking features behind subscription tiers. Can't share your screen at more than 720p unless you upgrade. File size limited to 8 MB. Want noise suppression? That's a premium feature now.",
|
||||
"paragraph2": "We refuse to play that game. <strong class=\"text-foreground\">Every feature in Toju is available to every user, always.</strong> There is no \"Toju Nitro,\" no \"Pro plan,\" no artificial limitations designed to push you toward your wallet. Communication is a human need, not a luxury - and the tools for it should reflect that."
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Privacy Is a Right, Not a Feature",
|
||||
"paragraph1": "Most communication platforms collect everything: who you talk to, when, for how long, what you share. They build profiles of your social graph, your habits, your interests. Even services that claim to care about privacy still store metadata on their servers.",
|
||||
"paragraph2": "Toju is architecturally different. Your data goes directly from your device to your friend's device. We don't have your messages. We don't have your files. We don't have your call history. Not because we promised not to look - but because the data never touches our infrastructure. We built the technology so that <strong class=\"text-foreground\">privacy isn't something we offer; it's something we literally cannot violate.</strong>"
|
||||
},
|
||||
"heart": {
|
||||
"title": "Built from the Heart",
|
||||
"paragraph1": "Toju wasn't born in a boardroom with revenue projections. It was born from frustration - frustration with being the product, with watching friends get locked out of features they used to have for free, with the growing feeling that the tools we depend on daily don't actually serve our interests.",
|
||||
"paragraph2": "We build Toju because we genuinely want to make the world a little better. The internet was supposed to connect people freely, and somewhere along the way, that mission got hijacked by business models that exploit the very connections they facilitate. <strong class=\"text-foreground\">Toju is our small act of reclaiming that original promise.</strong>"
|
||||
},
|
||||
"openSource": {
|
||||
"title": "Transparent by Default",
|
||||
"paragraph1": "Every line of Toju's code is publicly available. You can read it, audit it, contribute to it, or fork it and build your own version. This isn't a marketing decision - it's an accountability decision. When you can see exactly how the software works, you never have to take our word for anything.",
|
||||
"paragraph2": "Open source also means Toju belongs to its community, not to a company. Even if we stopped development tomorrow, the project lives on. Your communication infrastructure shouldn't depend on a single organization's survival."
|
||||
}
|
||||
},
|
||||
"promise": {
|
||||
"title": "Our Promise",
|
||||
"items": {
|
||||
"noPaywalls": "We will <strong class=\"text-foreground\">never</strong> lock features behind a paywall.",
|
||||
"noDataSales": "We will <strong class=\"text-foreground\">never</strong> sell, monetize, or harvest your data.",
|
||||
"openSource": "We will <strong class=\"text-foreground\">always</strong> keep the source code open and auditable.",
|
||||
"usersBeforeProfit": "We will <strong class=\"text-foreground\">always</strong> put users before profit."
|
||||
},
|
||||
"signature": "- The Myxelium team"
|
||||
},
|
||||
"support": {
|
||||
"title": "Help us keep going",
|
||||
"description": "If Toju's mission resonates with you, consider supporting the project. Every contribution helps us keep the lights on and development moving forward - without ever compromising our values."
|
||||
}
|
||||
},
|
||||
"whatIsToju": {
|
||||
"seo": {
|
||||
"title": "What is Toju? - How It Works",
|
||||
"description": "Learn how Toju's peer-to-peer technology delivers free, private, unlimited voice calls, screen sharing, and file transfers without centralized servers."
|
||||
},
|
||||
"hero": {
|
||||
"badge": "The Big Picture",
|
||||
"titlePrefix": "What is",
|
||||
"description": "Toju is a communication app that lets you voice chat, share your screen, send files, and message your friends - all without your data passing through someone else's servers. Think of it as your own private phone line that nobody can tap into."
|
||||
},
|
||||
"howItWorks": {
|
||||
"titlePrefix": "How does it",
|
||||
"titleHighlight": "work"
|
||||
},
|
||||
"steps": {
|
||||
"one": {
|
||||
"title": "You connect directly to your friends",
|
||||
"description": "When you start a call or send a file on Toju, your data travels directly from your device to your friend's device. There's no company server in the middle storing your conversations, listening to your calls, or scanning your files. This is called <strong class=\"text-foreground\">peer-to-peer</strong> - it's like having a direct road between your houses instead of going through a toll booth."
|
||||
},
|
||||
"two": {
|
||||
"title": "A tiny helper gets you connected",
|
||||
"description": "The only thing a server does is help your device find your friend's device - like a mutual friend introducing you at a party. Once you're connected, the server steps out of the picture entirely. It never sees what you say, share, or send. This helper is called a <strong class=\"text-foreground\">signal server</strong>, and you can even run your own if you'd like."
|
||||
},
|
||||
"three": {
|
||||
"title": "No limits because there are no middlemen",
|
||||
"description": "Since your data doesn't pass through our servers, we don't need to pay for massive infrastructure. That's why Toju can offer <strong class=\"text-foreground\">unlimited screen sharing, file transfers of any size, and high-quality voice</strong> - all completely free. There's no business reason to limit what you can do, and we never will."
|
||||
}
|
||||
},
|
||||
"whyDesigned": {
|
||||
"titlePrefix": "Why is it",
|
||||
"titleHighlight": "designed",
|
||||
"titleSuffix": "this way?"
|
||||
},
|
||||
"benefits": {
|
||||
"privacyArchitecture": {
|
||||
"title": "Privacy by Architecture",
|
||||
"description": "We didn't just add privacy as a feature - we built the entire app around it. When there's no central server handling your data, there's nothing to hack, subpoena, or sell. Your privacy isn't protected by a promise; it's protected by how the technology works."
|
||||
},
|
||||
"performance": {
|
||||
"title": "Performance Without Compromise",
|
||||
"description": "Direct connections mean lower latency. Your voice reaches your friend faster. Your screen share is smoother. Your file arrives in the time it actually takes to transfer - not in the time it takes to upload, store, then download."
|
||||
},
|
||||
"sustainable": {
|
||||
"title": "Sustainable & Free",
|
||||
"description": "Running a traditional chat service with millions of users costs enormous amounts of money for servers. That cost gets passed to you. With peer-to-peer, the only infrastructure we run is a tiny coordination server - costing almost nothing. That's how we keep it free permanently."
|
||||
},
|
||||
"independence": {
|
||||
"title": "Independence & Freedom",
|
||||
"description": "You're not locked into our ecosystem. The code is open source. You can run your own server. If we ever disappeared tomorrow, you could still use Toju. Your communication tools should belong to you, not a corporation."
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"titlePrefix": "Common",
|
||||
"titleHighlight": "Questions",
|
||||
"items": {
|
||||
"free": {
|
||||
"question": "Is Toju really free? What's the catch?",
|
||||
"answer": "Yes, it's really free. There is no catch. Because Toju uses peer-to-peer connections, we don't need expensive server infrastructure. Our costs are minimal, and we fund development through community support and donations. Every feature is available to everyone."
|
||||
},
|
||||
"technical": {
|
||||
"question": "Do I need technical knowledge to use Toju?",
|
||||
"answer": "Not at all. Toju works like any other chat app - download it, create an account, and start talking. All the peer-to-peer magic happens behind the scenes. You just enjoy the benefits of better privacy, performance, and no limits."
|
||||
},
|
||||
"selfHost": {
|
||||
"question": "What does \"self-host the signal server\" mean?",
|
||||
"answer": "The signal server is a tiny program that helps users find each other online. We run one by default, but if you prefer complete control, you can run your own copy on your own hardware. It's like having your own private phone directory - only people you invite can use it."
|
||||
},
|
||||
"safe": {
|
||||
"question": "Is my data safe?",
|
||||
"answer": "Your conversations, files, and calls go directly between you and the person you're talking to. They never pass through or get stored on our servers. Even if someone broke into our server, there would be nothing to find - because we never had your data in the first place."
|
||||
}
|
||||
}
|
||||
},
|
||||
"cta": {
|
||||
"title": "Ready to try it?",
|
||||
"description": "Available on Windows, Linux, and in your browser. Always free."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { HeaderComponent } from './components/header/header.component';
|
||||
import { FooterComponent } from './components/footer/footer.component';
|
||||
import { ParticleBgComponent } from './components/particle-bg/particle-bg.component';
|
||||
import translationsEn from '../../public/i18n/en.json';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -16,5 +18,13 @@ import { ParticleBgComponent } from './components/particle-bg/particle-bg.compon
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss'
|
||||
})
|
||||
export class AppComponent {}
|
||||
export class AppComponent {
|
||||
private readonly translate = inject(TranslateService);
|
||||
|
||||
constructor() {
|
||||
this.translate.setTranslation('en', translationsEn);
|
||||
this.translate.setFallbackLang('en');
|
||||
this.translate.use('en');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter, withInMemoryScrolling } from '@angular/router';
|
||||
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||
import { provideHttpClient, withFetch } from '@angular/common/http';
|
||||
import { provideTranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
|
||||
@@ -13,6 +14,10 @@ export const appConfig: ApplicationConfig = {
|
||||
withInMemoryScrolling({ scrollPositionRestoration: 'top', anchorScrolling: 'enabled' })
|
||||
),
|
||||
provideClientHydration(withEventReplay()),
|
||||
provideHttpClient(withFetch())
|
||||
provideHttpClient(withFetch()),
|
||||
provideTranslateService({
|
||||
fallbackLang: 'en',
|
||||
lang: 'en'
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div
|
||||
class="rounded-lg border border-dashed border-border/50 bg-card/30 min-h-[90px] flex items-center justify-center text-xs text-muted-foreground/50"
|
||||
>
|
||||
Advertisement
|
||||
{{ 'components.adSlot.label' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AdService } from '../../services/ad.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ad-slot',
|
||||
standalone: true,
|
||||
imports: [TranslateModule],
|
||||
templateUrl: './ad-slot.component.html'
|
||||
})
|
||||
export class AdSlotComponent {
|
||||
|
||||
@@ -6,25 +6,25 @@
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<img
|
||||
src="/images/toju-logo-transparent.png"
|
||||
alt="Toju"
|
||||
[attr.alt]="'common.brand' | translate"
|
||||
class="h-8 w-auto object-contain"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground leading-relaxed">
|
||||
Free, open-source, peer-to-peer communication. Built by people who believe privacy is a right, not a premium feature.
|
||||
{{ 'components.footer.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Product -->
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold text-foreground mb-4 uppercase tracking-wider">Product</h4>
|
||||
<h4 class="text-sm font-semibold text-foreground mb-4 uppercase tracking-wider">{{ 'components.footer.sections.product' | translate }}</h4>
|
||||
<ul class="space-y-3">
|
||||
<li>
|
||||
<a
|
||||
routerLink="/downloads"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Downloads
|
||||
{{ 'components.footer.links.downloads' | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -34,7 +34,7 @@
|
||||
rel="noopener"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Web Version
|
||||
{{ 'components.footer.links.webVersion' | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -42,7 +42,7 @@
|
||||
routerLink="/what-is-toju"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
What is Toju?
|
||||
{{ 'components.footer.links.whatIsToju' | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -50,7 +50,7 @@
|
||||
routerLink="/gallery"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Image Gallery
|
||||
{{ 'components.footer.links.imageGallery' | translate }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
<!-- Community -->
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold text-foreground mb-4 uppercase tracking-wider">Community</h4>
|
||||
<h4 class="text-sm font-semibold text-foreground mb-4 uppercase tracking-wider">{{ 'components.footer.sections.community' | translate }}</h4>
|
||||
<ul class="space-y-3">
|
||||
<li>
|
||||
<a
|
||||
@@ -74,7 +74,7 @@
|
||||
height="16"
|
||||
class="w-4 h-4 object-contain"
|
||||
/>
|
||||
Source Code
|
||||
{{ 'components.footer.links.sourceCode' | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -91,7 +91,7 @@
|
||||
height="16"
|
||||
class="w-4 h-4 object-contain"
|
||||
/>
|
||||
GitHub
|
||||
{{ 'components.footer.links.github' | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -108,7 +108,7 @@
|
||||
height="16"
|
||||
class="w-4 h-4 object-contain"
|
||||
/>
|
||||
Support Us
|
||||
{{ 'components.footer.links.supportUs' | translate }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -116,30 +116,30 @@
|
||||
|
||||
<!-- Values -->
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold text-foreground mb-4 uppercase tracking-wider">Values</h4>
|
||||
<h4 class="text-sm font-semibold text-foreground mb-4 uppercase tracking-wider">{{ 'components.footer.sections.values' | translate }}</h4>
|
||||
<ul class="space-y-3">
|
||||
<li>
|
||||
<a
|
||||
routerLink="/philosophy"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Our Philosophy
|
||||
{{ 'components.footer.links.ourPhilosophy' | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li><span class="text-sm text-muted-foreground">100% Free Forever</span></li>
|
||||
<li><span class="text-sm text-muted-foreground">Open Source</span></li>
|
||||
<li><span class="text-sm text-muted-foreground">{{ 'components.footer.values.freeForever' | translate }}</span></li>
|
||||
<li><span class="text-sm text-muted-foreground">{{ 'components.footer.values.openSource' | translate }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 pt-8 border-t border-border/30 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<p class="text-xs text-muted-foreground">© {{ currentYear }} Myxelium. Toju is open-source software.</p>
|
||||
<p class="text-xs text-muted-foreground">{{ 'components.footer.copyright' | translate:{ year: currentYear } }}</p>
|
||||
<div class="flex items-center gap-4">
|
||||
<a
|
||||
href="https://git.azaaxin.com/myxelium/Toju"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
aria-label="View source code on Gitea"
|
||||
[attr.aria-label]="'components.footer.viewSourceOnGitea' | translate"
|
||||
class="text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<img
|
||||
@@ -154,7 +154,7 @@
|
||||
href="https://github.com/Myxelium"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
aria-label="View the project on GitHub"
|
||||
[attr.aria-label]="'components.footer.viewProjectOnGitHub' | translate"
|
||||
class="text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<img
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
standalone: true,
|
||||
imports: [RouterLink],
|
||||
imports: [RouterLink, TranslateModule],
|
||||
templateUrl: './footer.component.html'
|
||||
})
|
||||
export class FooterComponent {
|
||||
|
||||
@@ -6,21 +6,21 @@
|
||||
<!-- Logo -->
|
||||
<a
|
||||
routerLink="/"
|
||||
aria-label="Toju home"
|
||||
[attr.aria-label]="'components.header.homeAriaLabel' | translate"
|
||||
class="flex items-center group"
|
||||
>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<img
|
||||
src="/images/toju-logo-transparent.png"
|
||||
alt="Toju"
|
||||
[attr.alt]="'common.brand' | translate"
|
||||
class="h-9 w-auto object-contain drop-shadow-lg group-hover:opacity-90 transition-opacity"
|
||||
/>
|
||||
<span class="text-xl font-bold text-foreground">Toju</span>
|
||||
<span class="text-xl font-bold text-foreground">{{ 'common.brand' | translate }}</span>
|
||||
</span>
|
||||
<span
|
||||
class="ml-2 text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-purple-500/20 text-purple-400 border border-purple-500/30 uppercase tracking-wider"
|
||||
>
|
||||
Beta
|
||||
{{ 'components.header.beta' | translate }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
@@ -32,28 +32,28 @@
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Home
|
||||
{{ 'components.header.navigation.home' | translate }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="/what-is-toju"
|
||||
routerLinkActive="text-primary"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
What is Toju?
|
||||
{{ 'components.header.navigation.whatIsToju' | translate }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="/downloads"
|
||||
routerLinkActive="text-primary"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Downloads
|
||||
{{ 'components.header.navigation.downloads' | translate }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="/philosophy"
|
||||
routerLinkActive="text-primary"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Our Philosophy
|
||||
{{ 'components.header.navigation.philosophy' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
height="16"
|
||||
class="w-4 h-4 object-contain"
|
||||
/>
|
||||
Support Us
|
||||
{{ 'components.header.supportUs' | translate }}
|
||||
</a>
|
||||
<a
|
||||
href="https://web.toju.app/"
|
||||
@@ -80,7 +80,7 @@
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-2 px-5 py-2 rounded-lg bg-gradient-to-r from-purple-600 to-violet-600 text-white text-sm font-medium hover:from-purple-500 hover:to-violet-500 transition-all shadow-lg shadow-purple-500/25 hover:shadow-purple-500/40"
|
||||
>
|
||||
Use Web Version
|
||||
{{ 'components.header.useWebVersion' | translate }}
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
@@ -102,7 +102,7 @@
|
||||
type="button"
|
||||
class="md:hidden text-foreground p-2"
|
||||
(click)="mobileOpen.set(!mobileOpen())"
|
||||
aria-label="Toggle menu"
|
||||
[attr.aria-label]="'components.header.toggleMenu' | translate"
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
@@ -138,22 +138,22 @@
|
||||
<a
|
||||
routerLink="/"
|
||||
class="block text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>Home</a
|
||||
>{{ 'components.header.navigation.home' | translate }}</a
|
||||
>
|
||||
<a
|
||||
routerLink="/what-is-toju"
|
||||
class="block text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>What is Toju?</a
|
||||
>{{ 'components.header.navigation.whatIsToju' | translate }}</a
|
||||
>
|
||||
<a
|
||||
routerLink="/downloads"
|
||||
class="block text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>Downloads</a
|
||||
>{{ 'components.header.navigation.downloads' | translate }}</a
|
||||
>
|
||||
<a
|
||||
routerLink="/philosophy"
|
||||
class="block text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>Our Philosophy</a
|
||||
>{{ 'components.header.navigation.philosophy' | translate }}</a
|
||||
>
|
||||
<hr class="border-border/30" />
|
||||
<a
|
||||
@@ -169,7 +169,7 @@
|
||||
height="16"
|
||||
class="w-4 h-4 object-contain"
|
||||
/>
|
||||
Support Us
|
||||
{{ 'components.header.supportUs' | translate }}
|
||||
</a>
|
||||
<a
|
||||
href="https://web.toju.app/"
|
||||
@@ -177,7 +177,7 @@
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-2 px-5 py-2 rounded-lg bg-gradient-to-r from-purple-600 to-violet-600 text-white text-sm font-medium"
|
||||
>
|
||||
Use Web Version
|
||||
{{ 'components.header.useWebVersion' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -5,13 +5,18 @@ import {
|
||||
HostListener,
|
||||
PLATFORM_ID
|
||||
} from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
standalone: true,
|
||||
imports: [RouterLink, RouterLinkActive],
|
||||
imports: [
|
||||
RouterLink,
|
||||
RouterLinkActive,
|
||||
TranslateModule
|
||||
],
|
||||
templateUrl: './header.component.html'
|
||||
})
|
||||
export class HeaderComponent {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="min-h-screen pt-32 pb-20">
|
||||
<section class="container mx-auto px-6 mb-16">
|
||||
<div class="max-w-3xl mx-auto text-center">
|
||||
<h1 class="text-4xl md:text-6xl font-extrabold text-foreground mb-6">Download <span class="gradient-text">Toju</span></h1>
|
||||
<h1 class="text-4xl md:text-6xl font-extrabold text-foreground mb-6">{{ 'pages.downloads.hero.titlePrefix' | translate }} <span class="gradient-text">{{ 'common.brand' | translate }}</span></h1>
|
||||
<p class="text-lg text-muted-foreground leading-relaxed">
|
||||
Available for Windows, Linux, and in your browser. Always free, always the full experience.
|
||||
{{ 'pages.downloads.hero.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -18,10 +18,10 @@
|
||||
<div
|
||||
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20 text-purple-400 text-sm font-medium mb-4"
|
||||
>
|
||||
Recommended for you
|
||||
{{ 'pages.downloads.recommended.badge' | translate }}
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold text-foreground mb-2">Toju for {{ detectedOS().name }}</h2>
|
||||
<p class="text-muted-foreground mb-6">Version {{ latestRelease()!.tag_name }}</p>
|
||||
<h2 class="text-2xl font-bold text-foreground mb-2">{{ 'pages.downloads.recommended.title' | translate:{ os: getDetectedOsLabel() } }}</h2>
|
||||
<p class="text-muted-foreground mb-6">{{ 'pages.downloads.recommended.version' | translate:{ version: latestRelease()!.tag_name } }}</p>
|
||||
|
||||
@if (recommendedUrl()) {
|
||||
<a
|
||||
@@ -41,21 +41,21 @@
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
Download for {{ detectedOS().name }}
|
||||
{{ 'common.actions.downloadFor' | translate:{ os: getDetectedOsLabel() } }}
|
||||
</a>
|
||||
}
|
||||
|
||||
<p class="text-xs text-muted-foreground/60 mt-4">
|
||||
Or
|
||||
{{ 'pages.downloads.recommended.webVersionPrefix' | translate }}
|
||||
<a
|
||||
href="https://web.toju.app/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="underline hover:text-muted-foreground transition-colors"
|
||||
>
|
||||
use the web version
|
||||
{{ 'pages.downloads.recommended.webVersionLink' | translate }}
|
||||
</a>
|
||||
- no download required.
|
||||
{{ 'pages.downloads.recommended.webVersionSuffix' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@
|
||||
<section class="container mx-auto px-6 mb-20">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h2 class="text-2xl font-bold text-foreground mb-8 section-fade">
|
||||
All platforms <span class="text-muted-foreground font-normal text-lg">- {{ release.tag_name }}</span>
|
||||
{{ 'pages.downloads.allPlatforms.title' | translate }} <span class="text-muted-foreground font-normal text-lg">- {{ release.tag_name }}</span>
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-3 section-fade">
|
||||
@@ -84,7 +84,7 @@
|
||||
@if (getOsIcon(asset.name)) {
|
||||
<img
|
||||
[src]="getOsIcon(asset.name)"
|
||||
[alt]="releaseService.getAssetOS(asset.name) + ' icon'"
|
||||
[attr.alt]="'pages.downloads.allPlatforms.assetIconAlt' | translate:{ os: getAssetOSLabel(asset.name) }"
|
||||
width="32"
|
||||
height="32"
|
||||
class="w-8 h-8 object-contain invert"
|
||||
@@ -94,7 +94,7 @@
|
||||
<div>
|
||||
<p class="text-sm font-medium text-foreground group-hover:text-purple-400 transition-colors">{{ asset.name }}</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ releaseService.getAssetOS(asset.name) }} · {{ releaseService.formatBytes(asset.size) }}
|
||||
{{ getAssetOSLabel(asset.name) }} · {{ releaseService.formatBytes(asset.size) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -123,7 +123,7 @@
|
||||
@if (releases().length > 1) {
|
||||
<section class="container mx-auto px-6 mb-20">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h2 class="text-2xl font-bold text-foreground mb-8 section-fade">Previous Releases</h2>
|
||||
<h2 class="text-2xl font-bold text-foreground mb-8 section-fade">{{ 'pages.downloads.previousReleases.title' | translate }}</h2>
|
||||
|
||||
<div class="space-y-4 section-fade">
|
||||
@for (release of releases().slice(1); track release.tag_name) {
|
||||
@@ -147,7 +147,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-foreground">{{ release.name || release.tag_name }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ formatDate(release.published_at) }} · {{ release.assets.length }} files</p>
|
||||
<p class="text-xs text-muted-foreground">{{ formatDate(release.published_at) }} · {{ 'pages.downloads.previousReleases.fileCount' | translate:{ count: release.assets.length } }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<svg
|
||||
@@ -178,7 +178,7 @@
|
||||
@if (getOsIcon(asset.name)) {
|
||||
<img
|
||||
[src]="getOsIcon(asset.name)"
|
||||
[alt]="releaseService.getAssetOS(asset.name) + ' icon'"
|
||||
[attr.alt]="'pages.downloads.allPlatforms.assetIconAlt' | translate:{ os: getAssetOSLabel(asset.name) }"
|
||||
width="16"
|
||||
height="16"
|
||||
class="w-4 h-4 object-contain mr-1 invert"
|
||||
@@ -236,7 +236,7 @@
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
||||
></path>
|
||||
</svg>
|
||||
Fetching releases...
|
||||
{{ 'pages.downloads.loading' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -254,14 +254,14 @@
|
||||
d="M6.18 15.64a2.18 2.18 0 012.18 2.18C8.36 19 7.38 20 6.18 20 5 20 4 19 4 17.82a2.18 2.18 0 012.18-2.18M4 4.44A15.56 15.56 0 0119.56 20h-2.83A12.73 12.73 0 004 7.27V4.44m0 5.66a9.9 9.9 0 019.9 9.9h-2.83A7.07 7.07 0 004 12.93V10.1z"
|
||||
/>
|
||||
</svg>
|
||||
Stay updated with our
|
||||
{{ 'pages.downloads.rss.prefix' | translate }}
|
||||
<a
|
||||
href="https://git.azaaxin.com/myxelium/Toju/releases.rss"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="underline hover:text-foreground transition-colors"
|
||||
>
|
||||
RSS feed
|
||||
{{ 'pages.downloads.rss.link' | translate }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
ReleaseService,
|
||||
Release,
|
||||
@@ -21,7 +22,7 @@ import { getOsIconPath } from './os-icon.util';
|
||||
@Component({
|
||||
selector: 'app-downloads',
|
||||
standalone: true,
|
||||
imports: [AdSlotComponent],
|
||||
imports: [AdSlotComponent, TranslateModule],
|
||||
templateUrl: './downloads.component.html'
|
||||
})
|
||||
export class DownloadsComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@@ -29,7 +30,7 @@ export class DownloadsComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
readonly releases = signal<Release[]>([]);
|
||||
readonly latestRelease = signal<Release | null>(null);
|
||||
readonly detectedOS = signal<DetectedOS>({
|
||||
name: 'Linux',
|
||||
key: 'linux',
|
||||
icon: '🐧',
|
||||
filePattern: /\.AppImage$/i,
|
||||
ymlFile: 'latest-linux.yml'
|
||||
@@ -40,11 +41,10 @@ export class DownloadsComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private readonly seoService = inject(SeoService);
|
||||
private readonly scrollAnimation = inject(ScrollAnimationService);
|
||||
private readonly platformId = inject(PLATFORM_ID);
|
||||
private readonly translate = inject(TranslateService);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.update({
|
||||
title: 'Download Toju',
|
||||
description: 'Download Toju for Windows, Linux, or use the web version. Free peer-to-peer voice chat, screen sharing, and file transfers.',
|
||||
this.seoService.updateFromTranslations('pages.downloads.seo', {
|
||||
url: 'https://toju.app/downloads'
|
||||
});
|
||||
|
||||
@@ -89,6 +89,14 @@ export class DownloadsComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return getOsIconPath(name, size);
|
||||
}
|
||||
|
||||
getDetectedOsLabel(): string {
|
||||
return this.translate.instant(`common.os.${this.detectedOS().key}`);
|
||||
}
|
||||
|
||||
getAssetOSLabel(name: string): string {
|
||||
return this.translate.instant(`common.os.${this.releaseService.getAssetOSKey(name)}`);
|
||||
}
|
||||
|
||||
formatDate(dateStr: string): string {
|
||||
try {
|
||||
return new Date(dateStr).toLocaleDateString('en-US', {
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<div
|
||||
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20 text-purple-400 text-sm font-medium mb-6"
|
||||
>
|
||||
Image Gallery
|
||||
{{ 'pages.gallery.hero.badge' | translate }}
|
||||
</div>
|
||||
<h1 class="text-4xl md:text-6xl font-extrabold text-foreground mb-6">A closer look at <span class="gradient-text">Toju</span></h1>
|
||||
<h1 class="text-4xl md:text-6xl font-extrabold text-foreground mb-6">{{ 'pages.gallery.hero.titlePrefix' | translate }} <span class="gradient-text">{{ 'common.brand' | translate }}</span></h1>
|
||||
<p class="text-lg text-muted-foreground leading-relaxed">
|
||||
Explore screenshots of the app experience, from voice chat and media sharing to servers, rooms, and full-screen collaboration.
|
||||
{{ 'pages.gallery.hero.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="relative aspect-[16/9]">
|
||||
<img
|
||||
ngSrc="/images/screenshots/screenshot_main.png"
|
||||
alt="Toju main application screenshot"
|
||||
[attr.alt]="'pages.gallery.featured.imageAlt' | translate"
|
||||
fill
|
||||
priority
|
||||
sizes="(min-width: 1536px) 75vw, (min-width: 1280px) 90vw, 100vw"
|
||||
@@ -27,10 +27,10 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-background via-background/80 to-transparent p-6 md:p-8">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-purple-400 mb-2">Featured</p>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-2">The full Toju workspace</h2>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-purple-400 mb-2">{{ 'pages.gallery.featured.label' | translate }}</p>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-2">{{ 'pages.gallery.featured.title' | translate }}</h2>
|
||||
<p class="max-w-2xl text-sm md:text-base text-muted-foreground">
|
||||
See the main interface where rooms, messages, presence, and media all come together in one focused layout.
|
||||
{{ 'pages.gallery.featured.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,15 +52,15 @@
|
||||
<div class="relative aspect-video overflow-hidden">
|
||||
<img
|
||||
[ngSrc]="item.src"
|
||||
[alt]="item.title"
|
||||
[attr.alt]="item.titleKey | translate"
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1280px) 50vw, 33vw"
|
||||
class="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">{{ item.title }}</h3>
|
||||
<p class="text-sm text-muted-foreground leading-relaxed">{{ item.description }}</p>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">{{ item.titleKey | translate }}</h3>
|
||||
<p class="text-sm text-muted-foreground leading-relaxed">{{ item.descriptionKey | translate }}</p>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
@@ -72,14 +72,14 @@
|
||||
<div
|
||||
class="max-w-4xl mx-auto section-fade rounded-3xl border border-purple-500/20 bg-gradient-to-br from-purple-950/20 to-violet-950/20 p-8 md:p-10 text-center"
|
||||
>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-4">Want to see it in action?</h2>
|
||||
<p class="text-muted-foreground leading-relaxed mb-6">Download Toju or jump into the browser experience and explore the interface yourself.</p>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-4">{{ 'pages.gallery.cta.title' | translate }}</h2>
|
||||
<p class="text-muted-foreground leading-relaxed mb-6">{{ 'pages.gallery.cta.description' | translate }}</p>
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a
|
||||
routerLink="/downloads"
|
||||
class="inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-gradient-to-r from-purple-600 to-violet-600 text-white font-semibold hover:from-purple-500 hover:to-violet-500 transition-all shadow-lg shadow-purple-500/25"
|
||||
>
|
||||
Go to downloads
|
||||
{{ 'common.actions.goToDownloads' | translate }}
|
||||
</a>
|
||||
<a
|
||||
href="https://web.toju.app/"
|
||||
@@ -87,7 +87,7 @@
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-2 px-6 py-3 rounded-xl border border-border/50 bg-card/50 text-foreground font-medium hover:border-purple-500/30 transition-all"
|
||||
>
|
||||
Open web version
|
||||
{{ 'common.actions.openWebVersion' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
inject
|
||||
} from '@angular/core';
|
||||
import { isPlatformBrowser, NgOptimizedImage } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { AdSlotComponent } from '../../components/ad-slot/ad-slot.component';
|
||||
import { ScrollAnimationService } from '../../services/scroll-animation.service';
|
||||
@@ -14,8 +15,8 @@ import { SeoService } from '../../services/seo.service';
|
||||
|
||||
interface GalleryItem {
|
||||
src: string;
|
||||
title: string;
|
||||
description: string;
|
||||
titleKey: string;
|
||||
descriptionKey: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -23,6 +24,7 @@ interface GalleryItem {
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgOptimizedImage,
|
||||
TranslateModule,
|
||||
RouterLink,
|
||||
AdSlotComponent
|
||||
],
|
||||
@@ -32,38 +34,38 @@ export class GalleryComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
readonly galleryItems: GalleryItem[] = [
|
||||
{
|
||||
src: '/images/screenshots/screenshot_main.png',
|
||||
title: 'Main chat view',
|
||||
description: 'The core Toju experience with channels, messages, and direct communication tools.'
|
||||
titleKey: 'pages.gallery.items.mainChatView.title',
|
||||
descriptionKey: 'pages.gallery.items.mainChatView.description'
|
||||
},
|
||||
{
|
||||
src: '/images/screenshots/screenshare_gaming.png',
|
||||
title: 'Gaming screen share',
|
||||
description: 'Share gameplay, guides, and live moments with smooth full-resolution screen sharing.'
|
||||
titleKey: 'pages.gallery.items.gamingScreenShare.title',
|
||||
descriptionKey: 'pages.gallery.items.gamingScreenShare.description'
|
||||
},
|
||||
{
|
||||
src: '/images/screenshots/serverViewScreen.png',
|
||||
title: 'Server overview',
|
||||
description: 'Navigate servers and rooms with a layout designed for clarity and speed.'
|
||||
titleKey: 'pages.gallery.items.serverOverview.title',
|
||||
descriptionKey: 'pages.gallery.items.serverOverview.description'
|
||||
},
|
||||
{
|
||||
src: '/images/screenshots/music.png',
|
||||
title: 'Music and voice',
|
||||
description: 'Stay in sync with voice and media features in a focused, low-friction interface.'
|
||||
titleKey: 'pages.gallery.items.musicAndVoice.title',
|
||||
descriptionKey: 'pages.gallery.items.musicAndVoice.description'
|
||||
},
|
||||
{
|
||||
src: '/images/screenshots/videos.png',
|
||||
title: 'Video sharing',
|
||||
description: 'Preview and share visual content directly with your friends and communities.'
|
||||
titleKey: 'pages.gallery.items.videoSharing.title',
|
||||
descriptionKey: 'pages.gallery.items.videoSharing.description'
|
||||
},
|
||||
{
|
||||
src: '/images/screenshots/filedownload.png',
|
||||
title: 'File transfers',
|
||||
description: 'Move files quickly without artificial size limits or unnecessary hoops.'
|
||||
titleKey: 'pages.gallery.items.fileTransfers.title',
|
||||
descriptionKey: 'pages.gallery.items.fileTransfers.description'
|
||||
},
|
||||
{
|
||||
src: '/images/screenshots/gif.png',
|
||||
title: 'Rich media chat',
|
||||
description: 'Conversations stay lively with visual media support built right in.'
|
||||
titleKey: 'pages.gallery.items.richMediaChat.title',
|
||||
descriptionKey: 'pages.gallery.items.richMediaChat.description'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -72,9 +74,7 @@ export class GalleryComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private readonly platformId = inject(PLATFORM_ID);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.update({
|
||||
title: 'Toju Image Gallery',
|
||||
description: 'Browse screenshots of Toju and explore the interface for chat, file sharing, voice, and screen sharing.',
|
||||
this.seoService.updateFromTranslations('pages.gallery.seo', {
|
||||
url: 'https://toju.app/gallery'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,19 +16,19 @@
|
||||
class="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-purple-500/30 bg-purple-500/10 text-purple-400 text-sm font-medium mb-8 animate-fade-in"
|
||||
>
|
||||
<span class="w-2 h-2 rounded-full bg-purple-400 animate-pulse"></span>
|
||||
Currently in Beta - Free & Open Source
|
||||
{{ 'pages.home.hero.badge' | translate }}
|
||||
</div>
|
||||
|
||||
<h1 class="text-5xl md:text-7xl lg:text-8xl font-extrabold tracking-tight mb-6 animate-fade-in-up">
|
||||
<span class="text-foreground">Talk freely.</span><br />
|
||||
<span class="gradient-text">Own your voice.</span>
|
||||
<span class="text-foreground">{{ 'pages.home.hero.titleLine1' | translate }}</span><br />
|
||||
<span class="gradient-text">{{ 'pages.home.hero.titleLine2' | translate }}</span>
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto mb-10 leading-relaxed animate-fade-in-up"
|
||||
style="animation-delay: 0.2s"
|
||||
>
|
||||
Crystal-clear voice calls, unlimited screen sharing, and file transfers with no size limits. Peer-to-peer. Private. Completely free.
|
||||
{{ 'pages.home.hero.description' | translate }}
|
||||
</p>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
@@ -54,7 +54,7 @@
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
Download for {{ detectedOS().name }}
|
||||
{{ 'common.actions.downloadFor' | translate:{ os: getDetectedOsLabel() } }}
|
||||
<span class="text-sm opacity-75">{{ detectedOS().icon }}</span>
|
||||
</a>
|
||||
} @else {
|
||||
@@ -75,7 +75,7 @@
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
Download Toju
|
||||
{{ 'common.actions.downloadBrand' | translate }}
|
||||
</a>
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-2 px-8 py-4 rounded-xl border border-border/50 bg-card/50 text-foreground font-medium text-lg hover:bg-card hover:border-purple-500/30 transition-all backdrop-blur-sm"
|
||||
>
|
||||
Open in Browser
|
||||
{{ 'common.actions.openInBrowser' | translate }}
|
||||
<svg
|
||||
class="w-5 h-5 opacity-60"
|
||||
fill="none"
|
||||
@@ -107,11 +107,11 @@
|
||||
class="text-xs text-muted-foreground/60 animate-fade-in"
|
||||
style="animation-delay: 0.6s"
|
||||
>
|
||||
Version {{ latestVersion() }} ·
|
||||
{{ 'pages.home.hero.version' | translate:{ version: latestVersion() } }} ·
|
||||
<a
|
||||
routerLink="/downloads"
|
||||
class="underline hover:text-muted-foreground transition-colors"
|
||||
>All platforms</a
|
||||
>{{ 'pages.home.hero.allPlatforms' | translate }}</a
|
||||
>
|
||||
</p>
|
||||
}
|
||||
@@ -142,10 +142,10 @@
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="text-center mb-20 section-fade">
|
||||
<h2 class="text-3xl md:text-5xl font-bold text-foreground mb-4">
|
||||
Everything you need,<br />
|
||||
<span class="gradient-text">nothing you don't.</span>
|
||||
{{ 'pages.home.features.titleLine1' | translate }}<br />
|
||||
<span class="gradient-text">{{ 'pages.home.features.titleLine2' | translate }}</span>
|
||||
</h2>
|
||||
<p class="text-muted-foreground text-lg max-w-xl mx-auto">No bloat. No paywalls. Just the tools to connect with the people who matter.</p>
|
||||
<p class="text-muted-foreground text-lg max-w-xl mx-auto">{{ 'pages.home.features.description' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@@ -168,9 +168,9 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">HD Voice Calls</h3>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.voiceCalls.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Crystal-clear audio with built-in noise reduction. Hear every word, not the background. No quality compromises, ever.
|
||||
{{ 'pages.home.features.items.voiceCalls.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -194,10 +194,9 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">Screen Sharing</h3>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.screenSharing.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Share your screen at full resolution. No time limits, no quality downgrades. Perfect for pair programming, presentations, or showing your
|
||||
epic gameplay.
|
||||
{{ 'pages.home.features.items.screenSharing.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -221,9 +220,9 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">Unlimited File Sharing</h3>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.fileSharing.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Send files of any size directly to your friends. No upload limits, no compression. Your files go straight from you to them.
|
||||
{{ 'pages.home.features.items.fileSharing.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -246,10 +245,9 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">True Privacy</h3>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.privacy.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Peer-to-peer means your data goes directly between you and your friends. No servers storing your conversations. Your business is your
|
||||
business.
|
||||
{{ 'pages.home.features.items.privacy.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -273,9 +271,9 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">Open Source</h3>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.openSource.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Every line of code is public. Audit it, modify it, host your own signal server. Full transparency - nothing is hidden.
|
||||
{{ 'pages.home.features.items.openSource.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -299,9 +297,9 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">Completely Free</h3>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.home.features.items.free.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
No premium tiers. No paywalls. No "starter plans". Every feature is available to everyone, always. Made with love, not profit margins.
|
||||
{{ 'pages.home.features.items.free.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -338,15 +336,14 @@
|
||||
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
Built for Gamers
|
||||
{{ 'pages.home.gaming.badge' | translate }}
|
||||
</div>
|
||||
<h2 class="text-3xl md:text-5xl font-bold text-foreground mb-6">
|
||||
Your perfect<br />
|
||||
<span class="gradient-text">gaming companion.</span>
|
||||
{{ 'pages.home.gaming.titleLine1' | translate }}<br />
|
||||
<span class="gradient-text">{{ 'pages.home.gaming.titleLine2' | translate }}</span>
|
||||
</h2>
|
||||
<p class="text-lg text-muted-foreground leading-relaxed mb-8">
|
||||
Ultra-low latency voice chat that doesn't eat your bandwidth. Share your screen without frame drops. Send clips and files instantly. All
|
||||
while keeping your CPU free for what matters - winning.
|
||||
{{ 'pages.home.gaming.description' | translate }}
|
||||
</p>
|
||||
<ul class="space-y-4">
|
||||
<li class="flex items-center gap-3 text-muted-foreground">
|
||||
@@ -363,7 +360,7 @@
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<span>Low-latency peer-to-peer voice - no relay servers in the way</span>
|
||||
<span>{{ 'pages.home.gaming.bullets.lowLatency' | translate }}</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-muted-foreground">
|
||||
<svg
|
||||
@@ -379,7 +376,7 @@
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<span>AI-powered noise suppression - keyboard clatter stays out</span>
|
||||
<span>{{ 'pages.home.gaming.bullets.noiseSuppression' | translate }}</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-muted-foreground">
|
||||
<svg
|
||||
@@ -395,7 +392,7 @@
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<span>Full-resolution screen sharing at high FPS</span>
|
||||
<span>{{ 'pages.home.gaming.bullets.screenShare' | translate }}</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-muted-foreground">
|
||||
<svg
|
||||
@@ -411,7 +408,7 @@
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<span>Send replays and screenshots with no file size limit</span>
|
||||
<span>{{ 'pages.home.gaming.bullets.fileTransfers' | translate }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -422,11 +419,11 @@
|
||||
ngSrc="/images/screenshots/screenshare_gaming.png"
|
||||
fill
|
||||
priority
|
||||
alt="Toju gaming screen sharing preview"
|
||||
[attr.alt]="'pages.home.gaming.imageAlt' | translate"
|
||||
class="object-cover"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-background/80 via-transparent to-transparent"></div>
|
||||
<div class="absolute bottom-4 left-4 text-sm text-muted-foreground/60">Game on. No limits.</div>
|
||||
<div class="absolute bottom-4 left-4 text-sm text-muted-foreground/60">{{ 'pages.home.gaming.caption' | translate }}</div>
|
||||
</div>
|
||||
<!-- Glow effect -->
|
||||
<div class="absolute -inset-4 bg-purple-600/5 rounded-3xl blur-2xl -z-10"></div>
|
||||
@@ -457,22 +454,21 @@
|
||||
d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"
|
||||
/>
|
||||
</svg>
|
||||
Self-Hostable
|
||||
{{ 'pages.home.selfHostable.badge' | translate }}
|
||||
</div>
|
||||
<h2 class="text-3xl md:text-5xl font-bold text-foreground mb-6">
|
||||
Your infrastructure,<br />
|
||||
<span class="gradient-text">your rules.</span>
|
||||
{{ 'pages.home.selfHostable.titleLine1' | translate }}<br />
|
||||
<span class="gradient-text">{{ 'pages.home.selfHostable.titleLine2' | translate }}</span>
|
||||
</h2>
|
||||
<p class="text-lg text-muted-foreground leading-relaxed mb-8">
|
||||
Toju uses a lightweight coordination server to help peers find each other - that's it. Your actual conversations never touch a server. Want
|
||||
even more control? Run your own coordination server in minutes. Full independence, zero compromises.
|
||||
{{ 'pages.home.selfHostable.description' | translate }}
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a
|
||||
routerLink="/what-is-toju"
|
||||
class="inline-flex items-center gap-2 px-6 py-3 rounded-xl border border-border/50 bg-card/50 text-foreground font-medium hover:border-purple-500/30 transition-all"
|
||||
>
|
||||
Learn how it works
|
||||
{{ 'common.actions.learnHowItWorks' | translate }}
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
@@ -500,7 +496,7 @@
|
||||
height="16"
|
||||
class="w-4 h-4 object-contain"
|
||||
/>
|
||||
View source code
|
||||
{{ 'common.actions.viewSourceCode' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -511,22 +507,22 @@
|
||||
<section class="relative py-24">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-purple-950/20 via-violet-950/30 to-purple-950/20"></div>
|
||||
<div class="relative container mx-auto px-6 text-center section-fade">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-foreground mb-4">Ready to take back your conversations?</h2>
|
||||
<p class="text-muted-foreground text-lg mb-8 max-w-lg mx-auto">Join thousands choosing privacy, freedom, and real connection.</p>
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-foreground mb-4">{{ 'pages.home.cta.title' | translate }}</h2>
|
||||
<p class="text-muted-foreground text-lg mb-8 max-w-lg mx-auto">{{ 'pages.home.cta.description' | translate }}</p>
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
@if (downloadUrl()) {
|
||||
<a
|
||||
[href]="downloadUrl()"
|
||||
class="inline-flex items-center gap-2 px-8 py-4 rounded-xl bg-gradient-to-r from-purple-600 to-violet-600 text-white font-semibold hover:from-purple-500 hover:to-violet-500 transition-all shadow-2xl shadow-purple-500/25"
|
||||
>
|
||||
Download for {{ detectedOS().name }}
|
||||
{{ 'common.actions.downloadFor' | translate:{ os: getDetectedOsLabel() } }}
|
||||
</a>
|
||||
} @else {
|
||||
<a
|
||||
routerLink="/downloads"
|
||||
class="inline-flex items-center gap-2 px-8 py-4 rounded-xl bg-gradient-to-r from-purple-600 to-violet-600 text-white font-semibold hover:from-purple-500 hover:to-violet-500 transition-all shadow-2xl shadow-purple-500/25"
|
||||
>
|
||||
Download Toju
|
||||
{{ 'common.actions.downloadBrand' | translate }}
|
||||
</a>
|
||||
}
|
||||
<a
|
||||
@@ -535,7 +531,7 @@
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-2 px-8 py-4 rounded-xl border border-border/50 bg-card/50 text-foreground font-medium hover:border-purple-500/30 transition-all"
|
||||
>
|
||||
Try in Browser
|
||||
{{ 'common.actions.tryInBrowser' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import { isPlatformBrowser, NgOptimizedImage } from '@angular/common';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { ReleaseService, DetectedOS } from '../../services/release.service';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
@@ -20,6 +21,7 @@ import { ParallaxDirective } from '../../directives/parallax.directive';
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgOptimizedImage,
|
||||
TranslateModule,
|
||||
RouterLink,
|
||||
AdSlotComponent,
|
||||
ParallaxDirective
|
||||
@@ -28,7 +30,7 @@ import { ParallaxDirective } from '../../directives/parallax.directive';
|
||||
})
|
||||
export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
readonly detectedOS = signal<DetectedOS>({
|
||||
name: 'Linux',
|
||||
key: 'linux',
|
||||
icon: '🐧',
|
||||
filePattern: /\.AppImage$/i,
|
||||
ymlFile: 'latest-linux.yml'
|
||||
@@ -40,13 +42,10 @@ export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private readonly seoService = inject(SeoService);
|
||||
private readonly scrollAnimation = inject(ScrollAnimationService);
|
||||
private readonly platformId = inject(PLATFORM_ID);
|
||||
private readonly translate = inject(TranslateService);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.update({
|
||||
title: 'Free Peer-to-Peer Voice, Video & Chat',
|
||||
description:
|
||||
'Toju is a free, open-source, peer-to-peer communication app. Crystal-clear voice calls, unlimited screen sharing, '
|
||||
+ 'no file size limits, complete privacy.',
|
||||
this.seoService.updateFromTranslations('pages.home.seo', {
|
||||
url: 'https://toju.app/'
|
||||
});
|
||||
|
||||
@@ -74,4 +73,8 @@ export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
ngOnDestroy(): void {
|
||||
this.scrollAnimation.destroy();
|
||||
}
|
||||
|
||||
getDetectedOsLabel(): string {
|
||||
return this.translate.instant(`common.os.${this.detectedOS().key}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<div
|
||||
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20 text-purple-400 text-sm font-medium mb-6"
|
||||
>
|
||||
Our Manifesto
|
||||
{{ 'pages.philosophy.hero.badge' | translate }}
|
||||
</div>
|
||||
<h1 class="text-4xl md:text-6xl font-extrabold text-foreground mb-6">Why we <span class="gradient-text">build</span> Toju</h1>
|
||||
<p class="text-lg text-muted-foreground leading-relaxed">A letter from the people behind the project.</p>
|
||||
<h1 class="text-4xl md:text-6xl font-extrabold text-foreground mb-6">{{ 'pages.philosophy.hero.titlePrefix' | translate }} <span class="gradient-text">{{ 'pages.philosophy.hero.titleHighlight' | translate }}</span> {{ 'common.brand' | translate }}</h1>
|
||||
<p class="text-lg text-muted-foreground leading-relaxed">{{ 'pages.philosophy.hero.description' | translate }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -19,81 +19,58 @@
|
||||
<article class="max-w-3xl mx-auto prose prose-invert prose-lg">
|
||||
<!-- Ownership -->
|
||||
<div class="section-fade mb-16">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6 !mt-0">We Lost Something Important</h2>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6 !mt-0">{{ 'pages.philosophy.sections.ownership.title' | translate }}</h2>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Over the past two decades, something fundamental shifted. Our conversations, our memories, our connections - they stopped belonging to us.
|
||||
They live on servers we don't control, inside apps that treat our personal lives as data to be harvested, analyzed, and sold to the highest
|
||||
bidder.
|
||||
{{ 'pages.philosophy.sections.ownership.paragraph1' | translate }}
|
||||
</p>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
We gave up ownership of our digital lives so gradually that most of us didn't even notice. A "free" app here, a convenient service there -
|
||||
each one taking a little more of our privacy in exchange for convenience. Toju exists because we believe it's time to take it back.
|
||||
{{ 'pages.philosophy.sections.ownership.paragraph2' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- No predatory pricing -->
|
||||
<div class="section-fade mb-16">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6">No Paywalls. No Premium Tiers. Ever.</h2>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6">{{ 'pages.philosophy.sections.paywalls.title' | translate }}</h2>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
You know the playbook: launch a free product, build a user base, then start locking features behind subscription tiers. Can't share your
|
||||
screen at more than 720p unless you upgrade. File size limited to 8 MB. Want noise suppression? That's a premium feature now.
|
||||
</p>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
We refuse to play that game. <strong class="text-foreground">Every feature in Toju is available to every user, always.</strong>
|
||||
There is no "Toju Nitro," no "Pro plan," no artificial limitations designed to push you toward your wallet. Communication is a human need,
|
||||
not a luxury - and the tools for it should reflect that.
|
||||
{{ 'pages.philosophy.sections.paywalls.paragraph1' | translate }}
|
||||
</p>
|
||||
<p class="text-muted-foreground leading-relaxed" [innerHTML]="'pages.philosophy.sections.paywalls.paragraph2' | translate"></p>
|
||||
</div>
|
||||
|
||||
<app-ad-slot />
|
||||
|
||||
<!-- Privacy as a right -->
|
||||
<div class="section-fade mb-16">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6">Privacy Is a Right, Not a Feature</h2>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6">{{ 'pages.philosophy.sections.privacy.title' | translate }}</h2>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Most communication platforms collect everything: who you talk to, when, for how long, what you share. They build profiles of your social
|
||||
graph, your habits, your interests. Even services that claim to care about privacy still store metadata on their servers.
|
||||
</p>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Toju is architecturally different. Your data goes directly from your device to your friend's device. We don't have your messages. We don't
|
||||
have your files. We don't have your call history. Not because we promised not to look - but because the data never touches our
|
||||
infrastructure. We built the technology so that
|
||||
<strong class="text-foreground">privacy isn't something we offer; it's something we literally cannot violate.</strong>
|
||||
{{ 'pages.philosophy.sections.privacy.paragraph1' | translate }}
|
||||
</p>
|
||||
<p class="text-muted-foreground leading-relaxed" [innerHTML]="'pages.philosophy.sections.privacy.paragraph2' | translate"></p>
|
||||
</div>
|
||||
|
||||
<!-- Better world -->
|
||||
<div class="section-fade mb-16">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6">Built from the Heart</h2>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6">{{ 'pages.philosophy.sections.heart.title' | translate }}</h2>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Toju wasn't born in a boardroom with revenue projections. It was born from frustration - frustration with being the product, with watching
|
||||
friends get locked out of features they used to have for free, with the growing feeling that the tools we depend on daily don't actually
|
||||
serve our interests.
|
||||
</p>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
We build Toju because we genuinely want to make the world a little better. The internet was supposed to connect people freely, and somewhere
|
||||
along the way, that mission got hijacked by business models that exploit the very connections they facilitate.
|
||||
<strong class="text-foreground">Toju is our small act of reclaiming that original promise.</strong>
|
||||
{{ 'pages.philosophy.sections.heart.paragraph1' | translate }}
|
||||
</p>
|
||||
<p class="text-muted-foreground leading-relaxed" [innerHTML]="'pages.philosophy.sections.heart.paragraph2' | translate"></p>
|
||||
</div>
|
||||
|
||||
<!-- Open source -->
|
||||
<div class="section-fade mb-16">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6">Transparent by Default</h2>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6">{{ 'pages.philosophy.sections.openSource.title' | translate }}</h2>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Every line of Toju's code is publicly available. You can read it, audit it, contribute to it, or fork it and build your own version. This
|
||||
isn't a marketing decision - it's an accountability decision. When you can see exactly how the software works, you never have to take our
|
||||
word for anything.
|
||||
{{ 'pages.philosophy.sections.openSource.paragraph1' | translate }}
|
||||
</p>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Open source also means Toju belongs to its community, not to a company. Even if we stopped development tomorrow, the project lives on. Your
|
||||
communication infrastructure shouldn't depend on a single organization's survival.
|
||||
{{ 'pages.philosophy.sections.openSource.paragraph2' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Commitment -->
|
||||
<div class="section-fade rounded-2xl border border-purple-500/20 bg-gradient-to-br from-purple-950/20 to-violet-950/20 p-8 md:p-10">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6 !mt-0">Our Promise</h2>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-6 !mt-0">{{ 'pages.philosophy.promise.title' | translate }}</h2>
|
||||
<ul class="space-y-4 text-muted-foreground !list-none !pl-0">
|
||||
<li class="flex items-start gap-3">
|
||||
<svg
|
||||
@@ -109,7 +86,7 @@
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<span>We will <strong class="text-foreground">never</strong> lock features behind a paywall.</span>
|
||||
<span [innerHTML]="'pages.philosophy.promise.items.noPaywalls' | translate"></span>
|
||||
</li>
|
||||
<li class="flex items-start gap-3">
|
||||
<svg
|
||||
@@ -125,7 +102,7 @@
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<span>We will <strong class="text-foreground">never</strong> sell, monetize, or harvest your data.</span>
|
||||
<span [innerHTML]="'pages.philosophy.promise.items.noDataSales' | translate"></span>
|
||||
</li>
|
||||
<li class="flex items-start gap-3">
|
||||
<svg
|
||||
@@ -141,7 +118,7 @@
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<span>We will <strong class="text-foreground">always</strong> keep the source code open and auditable.</span>
|
||||
<span [innerHTML]="'pages.philosophy.promise.items.openSource' | translate"></span>
|
||||
</li>
|
||||
<li class="flex items-start gap-3">
|
||||
<svg
|
||||
@@ -157,10 +134,10 @@
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<span>We will <strong class="text-foreground">always</strong> put users before profit.</span>
|
||||
<span [innerHTML]="'pages.philosophy.promise.items.usersBeforeProfit' | translate"></span>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="text-muted-foreground mt-6 text-sm">- The Myxelium team</p>
|
||||
<p class="text-muted-foreground mt-6 text-sm">{{ 'pages.philosophy.promise.signature' | translate }}</p>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
@@ -168,10 +145,9 @@
|
||||
<!-- Support CTA -->
|
||||
<section class="container mx-auto px-6">
|
||||
<div class="section-fade max-w-2xl mx-auto text-center">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-4">Help us keep going</h2>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-4">{{ 'pages.philosophy.support.title' | translate }}</h2>
|
||||
<p class="text-muted-foreground mb-8 leading-relaxed">
|
||||
If Toju's mission resonates with you, consider supporting the project. Every contribution helps us keep the lights on and development moving
|
||||
forward - without ever compromising our values.
|
||||
{{ 'pages.philosophy.support.description' | translate }}
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a
|
||||
@@ -187,13 +163,13 @@
|
||||
height="20"
|
||||
class="w-5 h-5 object-contain"
|
||||
/>
|
||||
Buy us a coffee
|
||||
{{ 'common.actions.buyUsCoffee' | translate }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="/downloads"
|
||||
class="inline-flex items-center gap-2 px-6 py-3 rounded-xl border border-border/50 bg-card/50 text-foreground font-medium hover:border-purple-500/30 transition-all"
|
||||
>
|
||||
Download Toju
|
||||
{{ 'common.actions.downloadBrand' | translate }}
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
PLATFORM_ID
|
||||
} from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
import { ScrollAnimationService } from '../../services/scroll-animation.service';
|
||||
@@ -15,7 +16,11 @@ import { AdSlotComponent } from '../../components/ad-slot/ad-slot.component';
|
||||
@Component({
|
||||
selector: 'app-philosophy',
|
||||
standalone: true,
|
||||
imports: [RouterLink, AdSlotComponent],
|
||||
imports: [
|
||||
RouterLink,
|
||||
AdSlotComponent,
|
||||
TranslateModule
|
||||
],
|
||||
templateUrl: './philosophy.component.html'
|
||||
})
|
||||
export class PhilosophyComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@@ -24,11 +29,7 @@ export class PhilosophyComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private readonly platformId = inject(PLATFORM_ID);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.update({
|
||||
title: 'Our Philosophy - Why We Build Toju',
|
||||
description:
|
||||
'Toju exists because privacy is a right, not a premium feature. No paywalls, no data harvesting, no predatory '
|
||||
+ 'pricing. Learn why we build free, open-source communication tools.',
|
||||
this.seoService.updateFromTranslations('pages.philosophy.seo', {
|
||||
url: 'https://toju.app/philosophy'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
<div
|
||||
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20 text-purple-400 text-sm font-medium mb-6"
|
||||
>
|
||||
The Big Picture
|
||||
{{ 'pages.whatIsToju.hero.badge' | translate }}
|
||||
</div>
|
||||
<h1 class="text-4xl md:text-6xl font-extrabold text-foreground mb-6">What is <span class="gradient-text">Toju</span>?</h1>
|
||||
<h1 class="text-4xl md:text-6xl font-extrabold text-foreground mb-6">{{ 'pages.whatIsToju.hero.titlePrefix' | translate }} <span class="gradient-text">{{ 'common.brand' | translate }}</span>?</h1>
|
||||
<p class="text-lg text-muted-foreground leading-relaxed">
|
||||
Toju is a communication app that lets you voice chat, share your screen, send files, and message your friends - all without your data passing
|
||||
through someone else's servers. Think of it as your own private phone line that nobody can tap into.
|
||||
{{ 'pages.whatIsToju.hero.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -21,7 +20,7 @@
|
||||
<section class="container mx-auto px-6 mb-24">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-foreground mb-12 text-center section-fade">
|
||||
How does it <span class="gradient-text">work</span>?
|
||||
{{ 'pages.whatIsToju.howItWorks.titlePrefix' | translate }} <span class="gradient-text">{{ 'pages.whatIsToju.howItWorks.titleHighlight' | translate }}</span>?
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-8">
|
||||
@@ -34,13 +33,8 @@
|
||||
1
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">You connect directly to your friends</h3>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
When you start a call or send a file on Toju, your data travels directly from your device to your friend's device. There's no company
|
||||
server in the middle storing your conversations, listening to your calls, or scanning your files. This is called
|
||||
<strong class="text-foreground">peer-to-peer</strong> - it's like having a direct road between your houses instead of going through a
|
||||
toll booth.
|
||||
</p>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.whatIsToju.steps.one.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed" [innerHTML]="'pages.whatIsToju.steps.one.description' | translate"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,12 +48,8 @@
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">A tiny helper gets you connected</h3>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
The only thing a server does is help your device find your friend's device - like a mutual friend introducing you at a party. Once
|
||||
you're connected, the server steps out of the picture entirely. It never sees what you say, share, or send. This helper is called a
|
||||
<strong class="text-foreground">signal server</strong>, and you can even run your own if you'd like.
|
||||
</p>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.whatIsToju.steps.two.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed" [innerHTML]="'pages.whatIsToju.steps.two.description' | translate"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,12 +63,8 @@
|
||||
3
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">No limits because there are no middlemen</h3>
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
Since your data doesn't pass through our servers, we don't need to pay for massive infrastructure. That's why Toju can offer
|
||||
<strong class="text-foreground">unlimited screen sharing, file transfers of any size, and high-quality voice</strong> - all completely
|
||||
free. There's no business reason to limit what you can do, and we never will.
|
||||
</p>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-3">{{ 'pages.whatIsToju.steps.three.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed" [innerHTML]="'pages.whatIsToju.steps.three.description' | translate"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,7 +78,7 @@
|
||||
<section class="container mx-auto px-6 mb-24">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-foreground mb-12 text-center section-fade">
|
||||
Why is it <span class="gradient-text">designed</span> this way?
|
||||
{{ 'pages.whatIsToju.whyDesigned.titlePrefix' | translate }} <span class="gradient-text">{{ 'pages.whatIsToju.whyDesigned.titleHighlight' | translate }}</span> {{ 'pages.whatIsToju.whyDesigned.titleSuffix' | translate }}
|
||||
</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-8">
|
||||
@@ -112,10 +98,9 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-3">Privacy by Architecture</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-3">{{ 'pages.whatIsToju.benefits.privacyArchitecture.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed text-sm">
|
||||
We didn't just add privacy as a feature - we built the entire app around it. When there's no central server handling your data, there's
|
||||
nothing to hack, subpoena, or sell. Your privacy isn't protected by a promise; it's protected by how the technology works.
|
||||
{{ 'pages.whatIsToju.benefits.privacyArchitecture.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -135,10 +120,9 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-3">Performance Without Compromise</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-3">{{ 'pages.whatIsToju.benefits.performance.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed text-sm">
|
||||
Direct connections mean lower latency. Your voice reaches your friend faster. Your screen share is smoother. Your file arrives in the time
|
||||
it actually takes to transfer - not in the time it takes to upload, store, then download.
|
||||
{{ 'pages.whatIsToju.benefits.performance.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -158,11 +142,9 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-3">Sustainable & Free</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-3">{{ 'pages.whatIsToju.benefits.sustainable.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed text-sm">
|
||||
Running a traditional chat service with millions of users costs enormous amounts of money for servers. That cost gets passed to you. With
|
||||
peer-to-peer, the only infrastructure we run is a tiny coordination server - costing almost nothing. That's how we keep it free
|
||||
permanently.
|
||||
{{ 'pages.whatIsToju.benefits.sustainable.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -182,10 +164,9 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-3">Independence & Freedom</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-3">{{ 'pages.whatIsToju.benefits.independence.title' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed text-sm">
|
||||
You're not locked into our ecosystem. The code is open source. You can run your own server. If we ever disappeared tomorrow, you could
|
||||
still use Toju. Your communication tools should belong to you, not a corporation.
|
||||
{{ 'pages.whatIsToju.benefits.independence.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -195,38 +176,34 @@
|
||||
<!-- FAQ-style section -->
|
||||
<section class="container mx-auto px-6 mb-24">
|
||||
<div class="max-w-3xl mx-auto section-fade">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-foreground mb-12 text-center">Common <span class="gradient-text">Questions</span></h2>
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-foreground mb-12 text-center">{{ 'pages.whatIsToju.faq.titlePrefix' | translate }} <span class="gradient-text">{{ 'pages.whatIsToju.faq.titleHighlight' | translate }}</span></h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="rounded-2xl border border-border/30 bg-card/30 backdrop-blur-sm p-6 md:p-8">
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">Is Toju really free? What's the catch?</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">{{ 'pages.whatIsToju.faq.items.free.question' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed text-sm">
|
||||
Yes, it's really free. There is no catch. Because Toju uses peer-to-peer connections, we don't need expensive server infrastructure. Our
|
||||
costs are minimal, and we fund development through community support and donations. Every feature is available to everyone.
|
||||
{{ 'pages.whatIsToju.faq.items.free.answer' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-border/30 bg-card/30 backdrop-blur-sm p-6 md:p-8">
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">Do I need technical knowledge to use Toju?</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">{{ 'pages.whatIsToju.faq.items.technical.question' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed text-sm">
|
||||
Not at all. Toju works like any other chat app - download it, create an account, and start talking. All the peer-to-peer magic happens
|
||||
behind the scenes. You just enjoy the benefits of better privacy, performance, and no limits.
|
||||
{{ 'pages.whatIsToju.faq.items.technical.answer' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-border/30 bg-card/30 backdrop-blur-sm p-6 md:p-8">
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">What does "self-host the signal server" mean?</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">{{ 'pages.whatIsToju.faq.items.selfHost.question' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed text-sm">
|
||||
The signal server is a tiny program that helps users find each other online. We run one by default, but if you prefer complete control,
|
||||
you can run your own copy on your own hardware. It's like having your own private phone directory - only people you invite can use it.
|
||||
{{ 'pages.whatIsToju.faq.items.selfHost.answer' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-border/30 bg-card/30 backdrop-blur-sm p-6 md:p-8">
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">Is my data safe?</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-2">{{ 'pages.whatIsToju.faq.items.safe.question' | translate }}</h3>
|
||||
<p class="text-muted-foreground leading-relaxed text-sm">
|
||||
Your conversations, files, and calls go directly between you and the person you're talking to. They never pass through or get stored on
|
||||
our servers. Even if someone broke into our server, there would be nothing to find - because we never had your data in the first place.
|
||||
{{ 'pages.whatIsToju.faq.items.safe.answer' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -238,14 +215,14 @@
|
||||
<div
|
||||
class="section-fade max-w-2xl mx-auto text-center rounded-2xl border border-purple-500/20 bg-gradient-to-br from-purple-950/20 to-violet-950/20 p-12"
|
||||
>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-4">Ready to try it?</h2>
|
||||
<p class="text-muted-foreground mb-8">Available on Windows, Linux, and in your browser. Always free.</p>
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-foreground mb-4">{{ 'pages.whatIsToju.cta.title' | translate }}</h2>
|
||||
<p class="text-muted-foreground mb-8">{{ 'pages.whatIsToju.cta.description' | translate }}</p>
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a
|
||||
routerLink="/downloads"
|
||||
class="inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-gradient-to-r from-purple-600 to-violet-600 text-white font-semibold hover:from-purple-500 hover:to-violet-500 transition-all shadow-lg shadow-purple-500/25"
|
||||
>
|
||||
Download Toju
|
||||
{{ 'common.actions.downloadBrand' | translate }}
|
||||
</a>
|
||||
<a
|
||||
href="https://web.toju.app/"
|
||||
@@ -253,7 +230,7 @@
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-2 px-6 py-3 rounded-xl border border-border/50 bg-card/50 text-foreground font-medium hover:border-purple-500/30 transition-all"
|
||||
>
|
||||
Open in Browser
|
||||
{{ 'common.actions.openInBrowser' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
PLATFORM_ID
|
||||
} from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
import { ScrollAnimationService } from '../../services/scroll-animation.service';
|
||||
@@ -15,7 +16,11 @@ import { AdSlotComponent } from '../../components/ad-slot/ad-slot.component';
|
||||
@Component({
|
||||
selector: 'app-what-is-toju',
|
||||
standalone: true,
|
||||
imports: [RouterLink, AdSlotComponent],
|
||||
imports: [
|
||||
RouterLink,
|
||||
AdSlotComponent,
|
||||
TranslateModule
|
||||
],
|
||||
templateUrl: './what-is-toju.component.html'
|
||||
})
|
||||
export class WhatIsTojuComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@@ -24,11 +29,7 @@ export class WhatIsTojuComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private readonly platformId = inject(PLATFORM_ID);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.update({
|
||||
title: 'What is Toju? - How It Works',
|
||||
description:
|
||||
'Learn how Toju\'s peer-to-peer technology delivers free, private, unlimited voice calls, screen sharing, and '
|
||||
+ 'file transfers without centralized servers.',
|
||||
this.seoService.updateFromTranslations('pages.whatIsToju.seo', {
|
||||
url: 'https://toju.app/what-is-toju'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,8 +20,10 @@ export interface Release {
|
||||
html_url: string;
|
||||
}
|
||||
|
||||
export type OsKey = 'windows' | 'macos' | 'linux' | 'linuxDebian' | 'archive' | 'web' | 'other';
|
||||
|
||||
export interface DetectedOS {
|
||||
name: string;
|
||||
key: 'windows' | 'macos' | 'linux' | 'linuxDebian';
|
||||
icon: string | null;
|
||||
filePattern: RegExp;
|
||||
ymlFile: string;
|
||||
@@ -78,7 +80,7 @@ export class ReleaseService {
|
||||
detectOS(): DetectedOS {
|
||||
if (!isPlatformBrowser(this.platformId)) {
|
||||
return {
|
||||
name: 'Linux',
|
||||
key: 'linux',
|
||||
icon: null,
|
||||
filePattern: /\.AppImage$/i,
|
||||
ymlFile: 'latest-linux.yml'
|
||||
@@ -89,7 +91,7 @@ export class ReleaseService {
|
||||
|
||||
if (userAgent.includes('win')) {
|
||||
return {
|
||||
name: 'Windows',
|
||||
key: 'windows',
|
||||
icon: null,
|
||||
filePattern: /\.exe$/i,
|
||||
ymlFile: 'latest.yml'
|
||||
@@ -98,7 +100,7 @@ export class ReleaseService {
|
||||
|
||||
if (userAgent.includes('mac')) {
|
||||
return {
|
||||
name: 'macOS',
|
||||
key: 'macos',
|
||||
icon: null,
|
||||
filePattern: /\.dmg$/i,
|
||||
ymlFile: 'latest-mac.yml'
|
||||
@@ -109,7 +111,7 @@ export class ReleaseService {
|
||||
|
||||
if (isUbuntuDebian) {
|
||||
return {
|
||||
name: 'Linux (deb)',
|
||||
key: 'linuxDebian',
|
||||
icon: null,
|
||||
filePattern: /\.deb$/i,
|
||||
ymlFile: 'latest-linux.yml'
|
||||
@@ -117,7 +119,7 @@ export class ReleaseService {
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'Linux',
|
||||
key: 'linux',
|
||||
icon: null,
|
||||
filePattern: /\.AppImage$/i,
|
||||
ymlFile: 'latest-linux.yml'
|
||||
@@ -162,30 +164,30 @@ export class ReleaseService {
|
||||
return matchingAsset?.browser_download_url ?? null;
|
||||
}
|
||||
|
||||
getAssetOS(name: string): string {
|
||||
getAssetOSKey(name: string): OsKey {
|
||||
const lower = name.toLowerCase();
|
||||
|
||||
if (matchesAssetPattern(lower, WINDOWS_SUFFIXES, WINDOWS_HINTS)) {
|
||||
return 'Windows';
|
||||
return 'windows';
|
||||
}
|
||||
|
||||
if (matchesAssetPattern(lower, MAC_SUFFIXES, MAC_HINTS)) {
|
||||
return 'macOS';
|
||||
return 'macos';
|
||||
}
|
||||
|
||||
if (matchesAssetPattern(lower, LINUX_SUFFIXES, LINUX_HINTS)) {
|
||||
return 'Linux';
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
if (matchesAssetPattern(lower, ARCHIVE_SUFFIXES)) {
|
||||
return 'Archive';
|
||||
return 'archive';
|
||||
}
|
||||
|
||||
if (lower.endsWith('.wasm')) {
|
||||
return 'Web';
|
||||
return 'web';
|
||||
}
|
||||
|
||||
return 'Other';
|
||||
return 'other';
|
||||
}
|
||||
|
||||
formatBytes(bytes: number): string {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Meta, Title } from '@angular/platform-browser';
|
||||
|
||||
interface SeoData {
|
||||
@@ -12,6 +13,7 @@ interface SeoData {
|
||||
export class SeoService {
|
||||
private readonly meta = inject(Meta);
|
||||
private readonly title = inject(Title);
|
||||
private readonly translate = inject(TranslateService);
|
||||
|
||||
update(data: SeoData): void {
|
||||
const fullTitle = `${data.title} - Toju`;
|
||||
@@ -34,4 +36,12 @@ export class SeoService {
|
||||
this.meta.updateTag({ name: 'twitter:image', content: data.image });
|
||||
}
|
||||
}
|
||||
|
||||
updateFromTranslations(keyPrefix: string, options: Omit<SeoData, 'title' | 'description'> = {}): void {
|
||||
this.update({
|
||||
title: this.translate.instant(`${keyPrefix}.title`),
|
||||
description: this.translate.instant(`${keyPrefix}.description`),
|
||||
...options
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"src/server.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
"src/**/*.d.ts",
|
||||
"public/i18n/**/*.json"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
"src/**/*.d.ts",
|
||||
"public/i18n/**/*.json"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user