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

This commit is contained in:
2026-03-13 03:45:17 +01:00
parent 2b6e477c9a
commit 00adf39121
26 changed files with 788 additions and 286 deletions

View File

@@ -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 &amp; 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>

View File

@@ -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}`);
}
}