feat(www): add Russian localization for homepage and articles
Complete Russian translations for homepage copy and all current www articles so the RU locale has native-quality content parity with EN. Made-with: Cursor
This commit is contained in:
1 parent
bb4c71d51c
commit
561118b274
5 files changed
+539
-41
No files matched your search
@@ -5,6 +5,276 @@ createdDate: 2026-02-15
|
||||
tags: ['matrix', 'deployment', 'guide']
|
||||
---
|
||||
|
||||
{/* TODO: Russian translation pending */}
|
||||
Это руководство предназначено **только для образовательных целей**. В нем показано, как развернуть приватную коммуникационную инфраструктуру, чтобы лучше понять децентрализованный протокол Matrix и техническую реализацию медиа-релеев.
|
||||
|
||||
> Это руководство предназначено **только для образовательных целей**.
|
||||
## Зачем поднимать сервер самостоятельно?
|
||||
|
||||
Централизованные платформы нередко жертвуют приватностью данных ради аналитики или зависят от внешних изменений политики. При развертывании собственного Matrix-инстанса вы получаете:
|
||||
|
||||
- **Суверенитет данных**: железо и база данных находятся под вашим контролем.
|
||||
- **Сквозное шифрование (E2EE)**: сообщения шифруются локально на устройствах, сервер передает только шифртекст.
|
||||
- **Устойчивость инфраструктуры**: ваши коммуникации не зависят от простоев крупных провайдеров.
|
||||
- **Приватность метаданных**: вы сами определяете, какие логи о взаимодействиях пользователей сохраняются.
|
||||
|
||||
---
|
||||
|
||||
## Обзор технического стека
|
||||
|
||||
1. **Synapse**: "homeserver", который реализует Matrix-протокол, логику учетных записей и синхронизацию сообщений.
|
||||
2. **Postgres**: база данных. Для production обязательна по соображениям производительности (SQLite подходит только для тестов).
|
||||
3. **Coturn (STUN/TURN)**: при звонках между двумя участниками фаерволы часто мешают прямому соединению. Coturn работает как релей и помогает обойти NAT-ограничения.
|
||||
4. **LiveKit**: современный медиадвижок (SFU). Если Coturn нужен в первую очередь для 1:1-звонков, то LiveKit эффективно обрабатывает групповые звонки через мультиплексирование аудио/видеопотоков.
|
||||
5. **lk-jwt-service**: сервис-аутентификатор. Проверяет, что пользователь авторизован в вашем Synapse-сервере, и выдает временный токен (JWT) для входа в звонок.
|
||||
|
||||
---
|
||||
|
||||
## 1. Стек развертывания
|
||||
|
||||
Эта конфигурация универсальна, использует **Docker Volumes** и настроена под **UID 1000:1000** для базового харднинга.
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
proxy:
|
||||
name: matrix-proxy
|
||||
internal:
|
||||
internal: true
|
||||
|
||||
volumes:
|
||||
synapse_data:
|
||||
postgres_data:
|
||||
traefik_certs:
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
container_name: matrix-proxy
|
||||
command:
|
||||
- '--providers.docker=true'
|
||||
- '--providers.docker.exposedbydefault=false'
|
||||
- '--entrypoints.web.address=:80'
|
||||
- '--entrypoints.websecure.address=:443'
|
||||
- '--entrypoints.web.http.redirections.entrypoint.to=websecure'
|
||||
- '--certificatesresolvers.myresolver.acme.dnschallenge.provider=cloudflare'
|
||||
- '--certificatesresolvers.myresolver.acme.email=${ADMIN_EMAIL}'
|
||||
- '--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json'
|
||||
environment:
|
||||
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
volumes:
|
||||
- traefik_certs:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
networks:
|
||||
- proxy
|
||||
restart: unless-stopped
|
||||
|
||||
synapse:
|
||||
image: ghcr.io/element-hq/synapse:v1.146.0
|
||||
container_name: matrix-synapse
|
||||
user: '1000:1000'
|
||||
depends_on:
|
||||
postgres: { condition: service_healthy }
|
||||
volumes:
|
||||
- synapse_data:/data
|
||||
environment:
|
||||
SYNAPSE_SERVER_NAME: '${DOMAIN}'
|
||||
SYNAPSE_REPORT_STATS: 'no'
|
||||
SYNAPSE_CONFIG_PATH: '/data/homeserver.yaml'
|
||||
networks:
|
||||
- proxy
|
||||
- internal
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.synapse.rule=Host(`${DOMAIN}`)'
|
||||
- 'traefik.http.services.synapse.loadbalancer.server.port=8008'
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: matrix-db
|
||||
user: '1000:1000'
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_DB: synapse
|
||||
POSTGRES_USER: synapse
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_INITDB_ARGS: '--encoding=UTF-8 --lc-collate=C --lc-ctype=C'
|
||||
networks:
|
||||
- internal
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U synapse']
|
||||
interval: 10s
|
||||
restart: unless-stopped
|
||||
|
||||
livekit:
|
||||
image: livekit/livekit-server:v1.9
|
||||
container_name: matrix-livekit
|
||||
command: --config /etc/livekit.yaml --node-ip ${SERVER_IP}
|
||||
environment:
|
||||
LIVEKIT_KEYS: '${LK_KEY}: ${LK_SECRET}'
|
||||
volumes:
|
||||
- ./livekit.yaml:/etc/livekit.yaml:ro
|
||||
ports:
|
||||
- '7880:7880'
|
||||
- '7881:7881'
|
||||
- '50000-50020:50000-50020/udp'
|
||||
networks:
|
||||
- proxy
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.lk.rule=Host(`${DOMAIN}`) && PathPrefix(`/livekit`)'
|
||||
- 'traefik.http.middlewares.lk-strip.stripprefix.prefixes=/livekit'
|
||||
- 'traefik.http.routers.lk.middlewares=lk-strip'
|
||||
restart: unless-stopped
|
||||
|
||||
lk-jwt:
|
||||
image: ghcr.io/element-hq/lk-jwt-service:0.4.1
|
||||
container_name: matrix-lk-jwt
|
||||
environment:
|
||||
LIVEKIT_URL: wss://${DOMAIN}/livekit
|
||||
LIVEKIT_KEY: '${LK_KEY}'
|
||||
LIVEKIT_SECRET: '${LK_SECRET}'
|
||||
networks:
|
||||
- proxy
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.lk-jwt.rule=Host(`${DOMAIN}`) && PathPrefix(`/livekit/jwt`)'
|
||||
- 'traefik.http.middlewares.lk-jwt-strip.stripprefix.prefixes=/livekit/jwt'
|
||||
- 'traefik.http.routers.lk-jwt.middlewares=lk-jwt-strip'
|
||||
restart: unless-stopped
|
||||
|
||||
coturn:
|
||||
image: coturn/coturn:4.8-alpine
|
||||
container_name: matrix-coturn
|
||||
user: '1000:1000'
|
||||
ports:
|
||||
- '3478:3478'
|
||||
- '3478:3478/udp'
|
||||
- '5349:5349'
|
||||
- '5349:5349/udp'
|
||||
- '49152-49172:49152-49172/udp'
|
||||
volumes:
|
||||
- ./turnserver.conf:/etc/coturn/turnserver.conf:ro
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Конфигурация
|
||||
|
||||
Создайте файл `.env` в той же директории. **Не пропускайте шаг генерации секретов ниже.**
|
||||
|
||||
```bash
|
||||
# --- Infrastructure ---
|
||||
DOMAIN=matrix.example.com
|
||||
SERVER_IP=1.2.3.4
|
||||
ADMIN_EMAIL=admin@example.com
|
||||
|
||||
# --- Cloudflare (for Traefik SSL) ---
|
||||
CF_DNS_API_TOKEN=your_cloudflare_api_token_here
|
||||
|
||||
# --- Database ---
|
||||
DB_PASSWORD=generate_this_with_openssl
|
||||
|
||||
# --- LiveKit Secrets ---
|
||||
# API Key can be any alphanumeric string
|
||||
LK_KEY=matrix_lk_key
|
||||
# Secret MUST be generated
|
||||
LK_SECRET=generate_this_with_openssl
|
||||
|
||||
# --- Coturn / Synapse VoIP ---
|
||||
# Used for STUN/TURN authentication
|
||||
TURN_SECRET=generate_this_with_openssl
|
||||
|
||||
```
|
||||
|
||||
### Генерация секретов
|
||||
|
||||
Используйте `openssl`, чтобы создать надежные случайные строки для параметров в `.env`:
|
||||
|
||||
```bash
|
||||
# Generate DB_PASSWORD, LK_SECRET, and TURN_SECRET
|
||||
openssl rand -hex 32
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Вариант с Nginx и сертификатами
|
||||
|
||||
Если вы не используете Traefik, получите сертификаты через **Certbot**:
|
||||
|
||||
```bash
|
||||
sudo certbot certonly --standalone -d matrix.example.com
|
||||
|
||||
```
|
||||
|
||||
### Конфиг Nginx
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name matrix.example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
|
||||
|
||||
location /_matrix {
|
||||
proxy_pass http://synapse:8008;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location /livekit/ {
|
||||
rewrite ^/livekit/(.*) /$1 break;
|
||||
proxy_pass http://livekit:7880;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
}
|
||||
|
||||
location /livekit/jwt/ {
|
||||
rewrite ^/livekit/jwt/(.*) /$1 break;
|
||||
proxy_pass http://lk-jwt:8080;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Регистрация пользователей
|
||||
|
||||
### Ручная регистрация (администратором)
|
||||
|
||||
Для приватных серверов отключите публичную регистрацию в `homeserver.yaml` и добавляйте пользователей через CLI:
|
||||
|
||||
```bash
|
||||
docker exec -it matrix-synapse register_new_matrix_user \
|
||||
-c /data/homeserver.yaml http://localhost:8008 \
|
||||
--user <username> --password <password> --admin
|
||||
|
||||
```
|
||||
|
||||
### SMTP (внешняя регистрация)
|
||||
|
||||
Добавьте в `homeserver.yaml`, чтобы включить подтверждение по email:
|
||||
|
||||
```yaml
|
||||
email:
|
||||
smtp_host: 'smtp.gmail.com'
|
||||
smtp_port: 587
|
||||
smtp_user: 'yourname@gmail.com'
|
||||
smtp_pass: 'xxxx xxxx xxxx xxxx' # Google App Password
|
||||
force_tls: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ресурсы
|
||||
|
||||
- [Synapse GitHub](https://github.com/element-hq/synapse)
|
||||
- [LiveKit GitHub](https://github.com/livekit/livekit)
|
||||
- [lk-jwt-service GitHub](https://github.com/element-hq/lk-jwt-service)
|
||||
- [Element X Apps](https://www.google.com/search?q=https://element.io/element-x)
|
||||
@@ -5,6 +5,11 @@ createdDate: 2026-01-31
|
||||
tags: ['test', 'general']
|
||||
---
|
||||
|
||||
{/* TODO: Russian translation pending */}
|
||||
## Раздел 1
|
||||
|
||||
> Это руководство предназначено **только для образовательных целей**.
|
||||
Немного содержимого здесь.
|
||||
|
||||
```typescript
|
||||
const hello = 'world';
|
||||
console.log(hello);
|
||||
```
|
||||
@@ -5,6 +5,178 @@ createdDate: 2026-03-08
|
||||
tags: ['networking', 'guide']
|
||||
---
|
||||
|
||||
{/* TODO: Russian translation pending */}
|
||||
> Это руководство предназначено **исключительно для образовательных целей**. Оно демонстрирует маршрутизацию в Xray-core и подход к мимикрии приватной подсети. Материал НЕ предназначен для обхода ограничений или незаконной деятельности. Используйте эти знания ответственно и в соответствии с местным законодательством.
|
||||
|
||||
> Это руководство предназначено **только для образовательных целей**.
|
||||
## Зачем нужна мимикрия подсети?
|
||||
|
||||
Если построить "плоскую сеть" (Layer 3) поверх публичного интернета, можно получить единое приватное адресное пространство (`10.0.0.0/8`), распределенное между несколькими отдельными серверами.
|
||||
|
||||
- **Распределенная топология**: каждый узел выступает в роли основного шлюза для своего сегмента `/16` (например, Hub для `10.1.0.0/16`, Spoke для `10.2.0.0/16` и `10.3.0.0/16`).
|
||||
- **Преодоление NAT**: Spoke-узлы (Bridges) сами инициируют соединения с Hub-узлом (Portal), благодаря чему даже узлы за NAT остаются доступными.
|
||||
- **Централизованное разрешение DNS**: сетевые DNS-политики единообразно применяются через центральный резолвер на Hub.
|
||||
|
||||
---
|
||||
|
||||
## Обзор технического стека
|
||||
|
||||
1. **Xray-core**: движок маршрутизации для VLESS-соединений, reverse-туннелей и TPROXY-перехвата.
|
||||
2. **TPROXY**: прозрачное проксирование в таблице Linux Mangle, которое перехватывает TCP/UDP-трафик и сохраняет исходный IP назначения.
|
||||
3. **Policy Routing**: использование `ip rule` и таблицы маршрутизации 100 для принудительной отправки перехваченных пакетов в локальный loopback, где их обрабатывает Xray.
|
||||
4. **Mux.cool**: протокол мультиплексирования, который упаковывает сотни виртуальных соединений (TCP/UDP-потоков) в одно физическое TLS-соединение.
|
||||
5. **AdGuard Home**: централизованный DNS-резолвер для всей подсети.
|
||||
6. **Nginx**: используется как маскировка на основном публичном входном порту, чтобы скрыть инстанс Xray.
|
||||
|
||||
---
|
||||
|
||||
## 1. Стек развертывания
|
||||
|
||||
Эта конфигурация описывает центральный Hub (сервер A), который выступает как Portal для подключений Spoke-узлов. На Hub размещены централизованный DNS и fallback-маскировка через Nginx.
|
||||
|
||||
```yaml
|
||||
name: 'a'
|
||||
|
||||
services:
|
||||
xray:
|
||||
image: ghcr.io/xtls/xray-core:26.1.23
|
||||
container_name: a-xray
|
||||
network_mode: host
|
||||
volumes:
|
||||
- ./xray.jsonc:/etc/xray/config.jsonc:ro
|
||||
- /etc/letsencrypt/live:/etc/letsencrypt/live:ro
|
||||
command: -c '/etc/xray/config.jsonc'
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
|
||||
nginx:
|
||||
image: nginxinc/nginx-unprivileged:1.29-alpine
|
||||
container_name: a-nginx
|
||||
networks:
|
||||
direct:
|
||||
ipv4_address: 10.1.1.4
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
adguard:
|
||||
image: adguard/adguardhome:v0.107.72
|
||||
container_name: root-adguard
|
||||
volumes:
|
||||
- adguard-work:/opt/adguardhome/work
|
||||
- adguard-conf:/opt/adguardhome/conf
|
||||
networks:
|
||||
direct:
|
||||
ipv4_address: 10.1.1.3
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
- SYS_NICE
|
||||
- CAP_CHOWN
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
networks:
|
||||
direct:
|
||||
name: a-direct
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 10.1.0.0/16 # Using the full /16 as defined for Server A
|
||||
gateway: 10.1.0.1
|
||||
|
||||
volumes:
|
||||
adguard-work:
|
||||
name: a-adguard-work
|
||||
adguard-conf:
|
||||
name: a-adguard-conf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Конфигурация и маршрутизация
|
||||
|
||||
### Генерация UUID-идентификаторов
|
||||
|
||||
Каждому соединению нужен собственный криптографический "ключ". Для сопоставления с конфигами вам понадобятся 4 UUID.
|
||||
|
||||
```bash
|
||||
# Generate UUIDs for the Server B and Server C mappings
|
||||
docker run --rm ghcr.io/xtls/xray-core uuid
|
||||
```
|
||||
|
||||
Соответствие такое:
|
||||
|
||||
- `UUID_B_REVERSE`: управляющий туннель для сервера B.
|
||||
- `UUID_B_DIRECT`: пользовательский трафик сервера B.
|
||||
- `UUID_C_REVERSE`: управляющий туннель для сервера C.
|
||||
- `UUID_C_DIRECT`: пользовательский трафик сервера C.
|
||||
|
||||
### Маршрутизация по политикам (таблица 100)
|
||||
|
||||
Примените скрипт маршрутизации на **каждом** серверном узле, чтобы настроить параметры ядра и таблицу маршрутизации 100. Это заставляет ядро передавать пакет в наш TPROXY inbound, который слушает `127.0.0.1:12345`.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 1. Enable IP Forwarding
|
||||
sudo sysctl -w net.ipv4.ip_forward=1
|
||||
|
||||
# 2. IP Rule for mark 0x1 -> table 100
|
||||
sudo ip rule add fwmark 0x1 lookup 100 priority 100
|
||||
|
||||
# 3. Local route in table 100
|
||||
sudo ip route add local 0.0.0.0/0 dev lo table 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Перехват в firewall
|
||||
|
||||
Откройте `/etc/ufw/before.rules` и сразу после обязательного заголовка `*filter` добавьте правила конкретного узла. Ниже пример для сервера A (Hub, `10.1.0.0/16`):
|
||||
|
||||
```text
|
||||
*mangle
|
||||
:XRAY - [0:0]
|
||||
:XRAY_MASK - [0:0]
|
||||
:DIVERT - [0:0]
|
||||
|
||||
# --- PREROUTING CHAIN (Traffic from other containers/nodes) ---
|
||||
-A XRAY -d 10.1.0.0/16 -j RETURN
|
||||
-A XRAY ! -d 10.0.0.0/8 -j RETURN
|
||||
-A XRAY -m mark --mark 0xff -j RETURN
|
||||
-A XRAY -p udp -j TPROXY --on-ip 127.0.0.1 --on-port 12345 --tproxy-mark 1
|
||||
-A XRAY -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port 12345 --tproxy-mark 1
|
||||
-A PREROUTING -j XRAY
|
||||
|
||||
# --- OUTPUT CHAIN (Traffic originating from the server itself) ---
|
||||
-A XRAY_MASK -d 10.1.0.0/16 -j RETURN
|
||||
-A XRAY_MASK ! -d 10.0.0.0/8 -j RETURN
|
||||
-A XRAY_MASK -m mark --mark 0xff -j RETURN
|
||||
-A XRAY_MASK -p udp -j MARK --set-mark 1
|
||||
-A XRAY_MASK -p tcp -j MARK --set-mark 1
|
||||
-A OUTPUT -j XRAY_MASK
|
||||
|
||||
# --- DIVERT Optimization ---
|
||||
-A DIVERT -j MARK --set-mark 1
|
||||
-A DIVERT -j ACCEPT
|
||||
-I PREROUTING -p tcp -m socket -j DIVERT
|
||||
|
||||
COMMIT
|
||||
```
|
||||
|
||||
После применения перезагрузите UFW:
|
||||
|
||||
```bash
|
||||
sudo ufw reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ресурсы
|
||||
|
||||
- [Официальная документация Xray-core](https://xtls.github.io/)
|
||||
- [Документация Xray Reverse Proxy](https://xtls.github.io/en/config/reverse.html)
|
||||
- [Project X (Xray) на GitHub](https://github.com/XTLS/Xray-core)
|
||||
- [Документация Linux Kernel по TPROXY](https://www.kernel.org/doc/Documentation/networking/tproxy.txt)
|
||||
- [Блог Cloudflare: TPROXY](https://blog.cloudflare.com/how-we-built-spectrum/)
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createKeyset } from '@ermnvldmr/i18n';
|
||||
|
||||
export const { t, tRaw } = createKeyset({
|
||||
'Thoughts and creations of mine': {
|
||||
en: 'Thoughts and creations of mine',
|
||||
ru: 'Мои мысли и проекты',
|
||||
},
|
||||
'Welcome to my corner of the internet.': {
|
||||
en: 'Welcome to my corner of the internet.',
|
||||
ru: 'Добро пожаловать в мой уголок интернета.',
|
||||
},
|
||||
'You arrived right as the lights turned on. The core foundation is solid and the site is officially live, but the metaphorical boxes are still being unpacked. Expect this space to evolve continuously as everything finds its proper place.':
|
||||
{
|
||||
en: 'You arrived right as the lights turned on. The core foundation is solid and the site is officially live, but the metaphorical boxes are still being unpacked. Expect this space to evolve continuously as everything finds its proper place.',
|
||||
ru: 'Вы зашли как раз в тот момент, когда здесь включили свет. Базовый фундамент уже крепкий, и сайт официально запущен, но метафорические коробки все еще распаковываются. Ожидайте, что это пространство будет постоянно развиваться, пока все не встанет на свои места.',
|
||||
},
|
||||
'Sharing the underlying architecture is a core philosophy here. If you are curious about the specifics of how it is all built, you can inspect the {repositoryLink} to see the complete blueprints.':
|
||||
{
|
||||
en: 'Sharing the underlying architecture is a core philosophy here. If you are curious about the specifics of how it is all built, you can inspect the {repositoryLink} to see the complete blueprints.',
|
||||
ru: 'Открыто рассказывать о внутренней архитектуре - один из ключевых принципов этого места. Если вам интересно, как именно все устроено, загляните в {repositoryLink}, чтобы увидеть полную картину.',
|
||||
},
|
||||
'ermnvldmr.com repository': {
|
||||
en: 'ermnvldmr.com repository',
|
||||
ru: 'репозиторий ermnvldmr.com',
|
||||
},
|
||||
'For a look at the world away from terminal screens and code editors, the {instagramLink} visual diary captures those moments.':
|
||||
{
|
||||
en: 'For a look at the world away from terminal screens and code editors, the {instagramLink} visual diary captures those moments.',
|
||||
ru: 'Если хочется посмотреть на мир вне терминалов и редакторов кода, эти моменты собраны в визуальном дневнике {instagramLink}.',
|
||||
},
|
||||
'Whenever you are ready to look around, feel free to dive into {articlesLink}. It is a dedicated space covering everything from technical deep-dives to everyday reflections.':
|
||||
{
|
||||
en: 'Whenever you are ready to look around, feel free to dive into {articlesLink}. It is a dedicated space covering everything from technical deep-dives to everyday reflections.',
|
||||
ru: 'Когда будете готовы осмотреться, загляните в {articlesLink}. Это отдельное пространство для всего - от глубоких технических разборов до повседневных наблюдений.',
|
||||
},
|
||||
'the growing collection of articles': {
|
||||
en: 'the growing collection of articles',
|
||||
ru: 'коллекцию статей, которая постоянно растет',
|
||||
},
|
||||
'Even though the rest of the environment is still a work in progress, the communication lines are fully operational. Whether you want to discuss tech, share a perspective on a recent read, or simply make a connection, the mailbox is open at...':
|
||||
{
|
||||
en: 'Even though the rest of the environment is still a work in progress, the communication lines are fully operational. Whether you want to discuss tech, share a perspective on a recent read, or simply make a connection, the mailbox is open at...',
|
||||
ru: 'Хотя остальная часть пространства еще дорабатывается, каналы связи уже работают в полном объеме. Если хотите обсудить технологии, поделиться мнением о прочитанном или просто познакомиться, почта всегда открыта:',
|
||||
},
|
||||
});
|
||||
@@ -2,16 +2,17 @@ import { localePath } from '@ermnvldmr/i18n';
|
||||
import { Paragraph, Link, VStack, Header } from '@ermnvldmr/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { t, tRaw } from './index.i18n';
|
||||
import { DefaultLayout } from '../components/Layout/DefaultLayout';
|
||||
import { createPage } from '../lib/core/createPage';
|
||||
import { SITE_DESCRIPTION, SITE_TITLE } from '../lib/shared/constants';
|
||||
import { SITE_TITLE } from '../lib/shared/constants';
|
||||
|
||||
/**
|
||||
* Home page component.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* createPage(HomePage, { title: SITE_TITLE, description: SITE_DESCRIPTION });
|
||||
* createPage(HomePage, { title: SITE_TITLE, description: t('Thoughts and creations of mine') });
|
||||
* ```
|
||||
*/
|
||||
function HomePage(): React.JSX.Element {
|
||||
@@ -23,49 +24,54 @@ function HomePage(): React.JSX.Element {
|
||||
>
|
||||
<VStack gap={24}>
|
||||
<Header delay={0} level={1}>
|
||||
Welcome to my corner of the internet.
|
||||
{t('Welcome to my corner of the internet.')}
|
||||
</Header>
|
||||
<VStack gap={12}>
|
||||
<Paragraph delay={300} size="l" type="title">
|
||||
You are arrived right as the lights turned on. The core foundation is solid and the site
|
||||
is officially live,
|
||||
<br />
|
||||
but the metaphorical boxes are still being unpacked. Expect this space to evolve
|
||||
continuously as everything finds its proper place.
|
||||
{t(
|
||||
'You arrived right as the lights turned on. The core foundation is solid and the site is officially live, but the metaphorical boxes are still being unpacked. Expect this space to evolve continuously as everything finds its proper place.'
|
||||
)}
|
||||
</Paragraph>
|
||||
<Paragraph delay={350} size="l" type="title">
|
||||
Sharing the underlying architecture is a core philosophy here. If you are curious about
|
||||
the specifics of how it is all built,
|
||||
<br />
|
||||
you can inspect the{' '}
|
||||
<Link href="https://github.com/deytenit/ermnvldmr.com/" size="l" type="title">
|
||||
ermnvldmr.com repository
|
||||
</Link>{' '}
|
||||
to see the complete blueprints.
|
||||
{tRaw(
|
||||
'Sharing the underlying architecture is a core philosophy here. If you are curious about the specifics of how it is all built, you can inspect the {repositoryLink} to see the complete blueprints.',
|
||||
{
|
||||
repositoryLink: (
|
||||
<Link href="https://github.com/deytenit/ermnvldmr.com/" size="l" type="title">
|
||||
{t('ermnvldmr.com repository')}
|
||||
</Link>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Paragraph>
|
||||
<Paragraph delay={400} size="l" type="title">
|
||||
For a look at the world away from terminal screens and code editors, the{' '}
|
||||
<Link href="https://ermnvldmr.com/instagram" size="l" type="title">
|
||||
@deytenit
|
||||
</Link>{' '}
|
||||
visual diary captures those moments.
|
||||
{tRaw(
|
||||
'For a look at the world away from terminal screens and code editors, the {instagramLink} visual diary captures those moments.',
|
||||
{
|
||||
instagramLink: (
|
||||
<Link href="https://ermnvldmr.com/instagram" size="l" type="title">
|
||||
@deytenit
|
||||
</Link>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Paragraph>
|
||||
<Paragraph delay={450} size="l" type="title">
|
||||
Whenever you are ready to look around, feel free to dive into{' '}
|
||||
<Link href={localePath('/articles')} size="l" type="title">
|
||||
the growing collection of articles
|
||||
</Link>
|
||||
.
|
||||
<br />
|
||||
It is a dedicated space covering everything from technical deep-dives to everyday
|
||||
reflections.
|
||||
{tRaw(
|
||||
'Whenever you are ready to look around, feel free to dive into {articlesLink}. It is a dedicated space covering everything from technical deep-dives to everyday reflections.',
|
||||
{
|
||||
articlesLink: (
|
||||
<Link href={localePath('/articles')} size="l" type="title">
|
||||
{t('the growing collection of articles')}
|
||||
</Link>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Paragraph>
|
||||
<Paragraph delay={500} size="l" type="title">
|
||||
Even though the rest of the environment is still a work in progress, the communication
|
||||
lines are fully operational.
|
||||
<br />
|
||||
Whether you want to discuss tech, share a perspective on a recent read, or simply make a
|
||||
connection, the mailbox is open at...
|
||||
{t(
|
||||
'Even though the rest of the environment is still a work in progress, the communication lines are fully operational. Whether you want to discuss tech, share a perspective on a recent read, or simply make a connection, the mailbox is open at...'
|
||||
)}
|
||||
</Paragraph>
|
||||
</VStack>
|
||||
<Link delay={1000} href="mailto:personal@ermnvldmr.com" size="l" type="title">
|
||||
@@ -76,5 +82,5 @@ function HomePage(): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
createPage(HomePage, { title: SITE_TITLE, description: SITE_DESCRIPTION });
|
||||
createPage(HomePage, { title: SITE_TITLE, description: t('Thoughts and creations of mine') });
|
||||
export default HomePage;
|
||||
Reference in new issue
Block a user