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:
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user