Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ jobs:
- name: Typecheck consoles
run: bunx turbo typecheck --filter=app --filter=admin

# Compares key sets and source hashes only — no network, no API key, so
# it runs on forks too. Fix a failure with `bun run i18n:sync`.
- name: Check i18n locale parity
run: bun run i18n:check

- name: Lint
run: bun run lint

Expand Down
84 changes: 84 additions & 0 deletions app/i18n.sync.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"version": 1,
"hashes": {
"agent:ru": {
"agent_name": "28145084c725",
"agent_resources": "e89b30aa1dc3",
"agent_status": "920e413c7d41",
"agent_template": "0575f29df888",
"agents": "279b44d2ab4b",
"create_agent": "5a614e73e098"
},
"bridle:ru": {
"chat.agent": "11b39c93777e",
"chat.empty_hint": "df21fa572470",
"chat.empty_title": "012c15d65531",
"chat.error": "a95d0d36ee79",
"chat.input_hint": "8a8c17c82d18",
"chat.placeholder": "57ef13894e3b",
"chat.send": "f6f4688ff23d",
"chat.sending": "a02f1cea3c1d",
"chat.starter_hint": "76085886cbdf",
"chat.you": "08b041935798"
},
"common:ru": {
"app_name": "d64517995073",
"auth.sign_in": "bfd402b2f6f3",
"auth.sign_out": "48f0d3d397d4",
"auth.sign_up": "5e2b8e96503d",
"demo.agent_greeting": "4741cc1329af",
"demo.agent_working": "9370717ab72c",
"demo.cpu": "db9a4c7d4c19",
"demo.memory": "c3963aedaac6",
"demo.user_request": "87467b7a12ed",
"features.admin_body": "9e1a6af2c7da",
"features.admin_title": "625b6464d0b0",
"features.chat_body": "7bc43aa29827",
"features.chat_title": "a0de0cf7dde9",
"features.logs_body": "1cadc89e731e",
"features.logs_title": "2763db1bbff3",
"features.restart_body": "d3abe64a1f92",
"features.restart_title": "9195a10357ba",
"features.status_body": "66b3943c194c",
"features.status_title": "4de30f8440dd",
"features.templates_body": "371112efb24e",
"features.templates_title": "96eccfa5c632",
"footer.note": "ab7218029e6a",
"hero.badge": "d1afeb3a5261",
"hero.cta_dashboard": "00519f8c9af0",
"hero.cta_deploy": "e852f76c4d13",
"hero.cta_sign_in": "04cca6b25913",
"hero.lede": "05932b878bcc",
"hero.stat_agents": "279b44d2ab4b",
"hero.stat_running": "f4ccae29e1bb",
"hero.stat_uptime": "d63ab4711473",
"hero.title": "9f28248b81cc",
"hero.title_accent": "a37b050de90d",
"landing.cta_button": "baad3429e418",
"landing.cta_lede": "8ef46042d616",
"landing.cta_title": "90c76b13ac6d",
"landing.how_eyebrow": "9c870aa6e5e9",
"landing.how_title": "c025c7422fa3",
"landing.step": "a0cc2a474806",
"landing.what_eyebrow": "9c8ea72883bb",
"landing.what_lede": "1efa22dfed12",
"landing.what_title": "c852637df453",
"locale.label": "a4fe65264ef7",
"nav.agents": "279b44d2ab4b",
"nav.history": "0e7696009337",
"nav.templates": "56b564b75c7f",
"steps.chat_body": "8f80dfeff166",
"steps.chat_title": "e5e09a778387",
"steps.deploy_body": "e358d64527e3",
"steps.deploy_title": "833c2e33e68c",
"steps.template_body": "ae90b69c7099",
"steps.template_title": "21a2714bf862"
},
"template:ru": {
"template_description": "526e0087cc3f",
"template_image": "140d5d5895d7",
"template_name": "f95e97c2f767",
"templates": "56b564b75c7f"
}
}
}
8 changes: 8 additions & 0 deletions app/slices/agent/i18n/locales/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"agents": "Агенты",
"create_agent": "Создать агента",
"agent_name": "Имя агента",
"agent_status": "Статус",
"agent_template": "Шаблон",
"agent_resources": "Ресурсы"
}
4 changes: 3 additions & 1 deletion app/slices/agent/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { fileURLToPath } from 'url';
import { dirname } from 'path';

import { LOCALES } from '../setup/i18n/locales';

const currentDir = dirname(fileURLToPath(import.meta.url));

export default defineNuxtConfig({
Expand All @@ -13,6 +15,6 @@ export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
langDir: 'locales',
locales: [{ code: 'en', file: 'en.json' }],
locales: LOCALES,
},
});
7 changes: 2 additions & 5 deletions app/slices/bridle/components/bridleChat/Empty.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
class="flex flex-col items-center justify-center h-full text-center text-muted-foreground px-6"
>
<div class="text-5xl mb-4">💬</div>
<h2 class="text-lg font-semibold mb-1">{{ t('chat.empty_title') }}</h2>
<p class="text-sm max-w-sm">{{ t('chat.empty_hint') }}</p>
<h2 class="text-lg font-semibold mb-1">{{ $t('chat.empty_title') }}</h2>
<p class="text-sm max-w-sm">{{ $t('chat.empty_hint') }}</p>
</div>
</template>

<script setup lang="ts">
const { t } = useI18n();
</script>
26 changes: 17 additions & 9 deletions app/slices/bridle/components/bridleChat/Input.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<script setup lang="ts">
const props = defineProps<{ disabled?: boolean }>();
const emit = defineEmits<{ send: [text: string] }>();
const { t } = useI18n();

const draft = ref('');
const textareaRef = ref<HTMLTextAreaElement | null>(null);
Expand Down Expand Up @@ -41,7 +40,7 @@ watch(draft, () => nextTick(autoResize));
<textarea
ref="textareaRef"
v-model="draft"
:placeholder="t('chat.placeholder')"
:placeholder="$t('chat.placeholder')"
rows="1"
class="flex-1 resize-none bg-transparent py-2 text-sm leading-relaxed placeholder:text-muted-foreground/60 focus:outline-none disabled:cursor-not-allowed"
:disabled="disabled"
Expand All @@ -51,7 +50,7 @@ watch(draft, () => nextTick(autoResize));
type="submit"
:disabled="!canSend"
class="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-primary text-primary-foreground transition hover:opacity-90 disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed"
:aria-label="t('chat.send')"
:aria-label="$t('chat.send')"
>
<Icon
v-if="disabled"
Expand All @@ -66,12 +65,21 @@ watch(draft, () => nextTick(autoResize));
/>
</button>
</div>
<p class="mt-1.5 px-1 text-[11px] text-muted-foreground/60">
<kbd class="rounded border bg-muted px-1 font-mono text-[10px]">Enter</kbd>
to send,
<kbd class="rounded border bg-muted px-1 font-mono text-[10px]">Shift+Enter</kbd>
for newline
</p>
<!-- i18n-t keeps the sentence one translatable string with the two key
caps as slots — translators reorder words, and splitting this into
fragments around the <kbd> tags would make that impossible. -->
<i18n-t
keypath="chat.input_hint"
tag="p"
class="mt-1.5 px-1 text-[11px] text-muted-foreground/60"
>
<template #enter>
<kbd class="rounded border bg-muted px-1 font-mono text-[10px]">Enter</kbd>
</template>
<template #shiftEnter>
<kbd class="rounded border bg-muted px-1 font-mono text-[10px]">Shift+Enter</kbd>
</template>
</i18n-t>
</div>
</form>
</template>
8 changes: 3 additions & 5 deletions app/slices/bridle/components/bridleChat/Provider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const props = withDefaults(
}>(),
{ showHeader: true },
);

const { t } = useI18n();
const bridleStore = useBridleStore();

// Replay persisted conversation so the chat isn't blank after a refresh.
Expand Down Expand Up @@ -104,10 +102,10 @@ const agentInitial = computed(() => {
{{ agentInitial }}
</div>
<h3 class="mt-3 text-sm font-semibold">
{{ title ?? t('chat.agent') }}
{{ title ?? $t('chat.agent') }}
</h3>
<p class="mt-1 max-w-xs text-xs text-muted-foreground">
Say hello — your message will go straight to the agent runtime.
{{ $t('chat.starter_hint') }}
</p>
</div>

Expand Down Expand Up @@ -144,7 +142,7 @@ const agentInitial = computed(() => {
class="flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/10 p-3 text-xs text-destructive"
>
<Icon name="alert-triangle" :size="14" class="mt-px shrink-0" />
<span>{{ t('chat.error') }}: {{ error }}</span>
<span>{{ $t('chat.error') }}: {{ error }}</span>
</div>
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions app/slices/bridle/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"chat": {
"empty_title": "Pick an agent",
"empty_hint": "Select one of your agents on the left to start chatting.",
"starter_hint": "Say hello — your message will go straight to the agent runtime.",
"placeholder": "Message your agent…",
"input_hint": "{enter} to send, {shiftEnter} for newline",
"send": "Send",
"sending": "Thinking…",
"error": "Failed to reach agent",
Expand Down
14 changes: 14 additions & 0 deletions app/slices/bridle/i18n/locales/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"chat": {
"empty_title": "Выберите агента",
"empty_hint": "Выберите одного из своих агентов слева, чтобы начать общение.",
"starter_hint": "Поздоровайтесь — сообщение сразу уйдёт в среду выполнения агента.",
"placeholder": "Сообщение агенту…",
"input_hint": "{enter} — отправить, {shiftEnter} — новая строка",
"send": "Отправить",
"sending": "Думает…",
"error": "Не удалось связаться с агентом",
"you": "Вы",
"agent": "Агент"
}
}
4 changes: 3 additions & 1 deletion app/slices/bridle/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { fileURLToPath } from 'url';
import { dirname } from 'path';

import { LOCALES } from '../setup/i18n/locales';

const currentDir = dirname(fileURLToPath(import.meta.url));

export default defineNuxtConfig({
Expand All @@ -13,6 +15,6 @@ export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
langDir: 'locales',
locales: [{ code: 'en', file: 'en.json' }],
locales: LOCALES,
},
});
10 changes: 5 additions & 5 deletions app/slices/common/components/landingHero/AgentCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@
<div
class="rounded-lg rounded-tl-none bg-muted px-3 py-2 text-sm max-w-[85%]"
>
Hi! I'm ready to help you ship.
{{ $t('demo.agent_greeting') }}
</div>
</div>
<div class="flex items-start gap-2 justify-end">
<div
class="rounded-lg rounded-tr-none bg-primary text-primary-foreground px-3 py-2 text-sm max-w-[85%]"
>
Deploy a new worker to staging.
{{ $t('demo.user_request') }}
</div>
<div
class="shrink-0 w-7 h-7 rounded-full bg-primary/20 flex items-center justify-center text-xs"
Expand All @@ -59,18 +59,18 @@
<div
class="rounded-lg rounded-tl-none bg-muted px-3 py-2 text-sm max-w-[85%] animate-pulse"
>
Submitting workflow…
{{ $t('demo.agent_working') }}
</div>
</div>
</div>

<div class="grid grid-cols-2 gap-2 text-xs">
<div class="rounded-md border px-3 py-2">
<div class="text-muted-foreground">CPU</div>
<div class="text-muted-foreground">{{ $t('demo.cpu') }}</div>
<div class="font-medium">{{ agent.resources?.cpu ?? '—' }}</div>
</div>
<div class="rounded-md border px-3 py-2">
<div class="text-muted-foreground">Memory</div>
<div class="text-muted-foreground">{{ $t('demo.memory') }}</div>
<div class="font-medium">{{ agent.resources?.memory ?? '—' }}</div>
</div>
</div>
Expand Down
28 changes: 16 additions & 12 deletions app/slices/common/components/landingHero/Provider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,61 @@
class="inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs text-muted-foreground mb-6"
>
<span class="w-2 h-2 rounded-full bg-green-500" />
Live on Kubernetes · Powered by Argo Workflows
{{ $t('hero.badge') }}
</div>
<h1
class="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight leading-[1.05]"
>
Deploy AI agents.
{{ $t('hero.title') }}
<br />
<span class="text-primary">Talk to them.</span>
<span class="text-primary">{{ $t('hero.title_accent') }}</span>
</h1>
<p class="mt-6 text-lg text-muted-foreground max-w-xl">
Ranch is your agent deployment platform. Spin up containerized
AI workers on a managed k3s cluster and chat with them in real
time — no DevOps required.
{{ $t('hero.lede') }}
</p>

<div class="mt-8 flex flex-wrap gap-3">
<NuxtLink
to="/agents"
class="inline-flex items-center justify-center rounded-md bg-primary text-primary-foreground px-5 py-3 text-sm font-medium hover:opacity-90 transition"
>
Open full dashboard →
{{ $t('hero.cta_dashboard') }}
</NuxtLink>
<NuxtLink
v-if="canDeployAgent"
to="/agents/create"
class="inline-flex items-center justify-center rounded-md border px-5 py-3 text-sm font-medium hover:bg-accent transition"
>
Deploy an agent
{{ $t('hero.cta_deploy') }}
</NuxtLink>
<NuxtLink
v-else-if="!authStore.isAuthenticated"
to="/login"
class="inline-flex items-center justify-center rounded-md border px-5 py-3 text-sm font-medium hover:bg-accent transition"
>
Sign in to deploy
{{ $t('hero.cta_sign_in') }}
</NuxtLink>
</div>

<dl class="mt-10 grid grid-cols-3 gap-6 max-w-md">
<div>
<dt class="text-xs text-muted-foreground">Agents</dt>
<dt class="text-xs text-muted-foreground">
{{ $t('hero.stat_agents') }}
</dt>
<dd class="text-2xl font-semibold">
{{ agentStore.publicAgents.length }}
</dd>
</div>
<div>
<dt class="text-xs text-muted-foreground">Running</dt>
<dt class="text-xs text-muted-foreground">
{{ $t('hero.stat_running') }}
</dt>
<dd class="text-2xl font-semibold">{{ runningCount }}</dd>
</div>
<div>
<dt class="text-xs text-muted-foreground">Uptime</dt>
<dt class="text-xs text-muted-foreground">
{{ $t('hero.stat_uptime') }}
</dt>
<dd class="text-2xl font-semibold">99.9%</dd>
</div>
</dl>
Expand Down
Loading
Loading