diff --git a/src/renderer/src/components/AppShell.tsx b/src/renderer/src/components/AppShell.tsx
index af2ef6f..9b8ec2e 100644
--- a/src/renderer/src/components/AppShell.tsx
+++ b/src/renderer/src/components/AppShell.tsx
@@ -15,6 +15,7 @@ import {
import type { LucideIcon } from "lucide-react";
import { ProfileView } from "../features/profile/ProfileView";
import { WorldView } from "../features/world/WorldView";
+import { InstanceView } from "../features/world/InstanceView";
import { GroupView } from "../features/group/GroupView";
import { AccountSettingsView } from "../features/account/AccountSettingsView";
import { SearchView } from "../features/search/SearchView";
@@ -164,6 +165,8 @@ function Shell() {
{nav.current.kind === "world" ? (
+ ) : nav.current.kind === "instance" ? (
+
) : nav.current.kind === "group" ? (
) : nav.current.kind === "account" ? (
diff --git a/src/renderer/src/features/navigation/NavContext.tsx b/src/renderer/src/features/navigation/NavContext.tsx
index 3bfda85..43cf559 100644
--- a/src/renderer/src/features/navigation/NavContext.tsx
+++ b/src/renderer/src/features/navigation/NavContext.tsx
@@ -3,6 +3,7 @@ import { createContext, useContext, useMemo, useState } from "react";
export type View =
| { kind: "user"; id: "me" | string }
| { kind: "world"; id: string }
+ | { kind: "instance"; worldId: string; instanceId: string; location: string }
| { kind: "group"; id: string }
| { kind: "account" }
| { kind: "settings" }
@@ -15,6 +16,7 @@ interface Nav {
canBack: boolean;
openUser: (id: "me" | string) => void;
openWorld: (id: string) => void;
+ openInstance: (worldId: string, instanceId: string, location: string) => void;
openGroup: (id: string) => void;
openAccount: () => void;
openSettings: () => void;
@@ -44,6 +46,8 @@ export function NavProvider({ children }: { children: React.ReactNode }) {
canBack: stack.length > 1,
openUser: (id) => push({ kind: "user", id }),
openWorld: (id) => push({ kind: "world", id }),
+ openInstance: (worldId, instanceId, location) =>
+ push({ kind: "instance", worldId, instanceId, location }),
openGroup: (id) => push({ kind: "group", id }),
openAccount: () => root({ kind: "account" }),
openSettings: () => root({ kind: "settings" }),
@@ -62,6 +66,7 @@ function sameView(a: View, b: View): boolean {
if (a.kind !== b.kind) return false;
if (a.kind === "user" && b.kind === "user") return a.id === b.id;
if (a.kind === "world" && b.kind === "world") return a.id === b.id;
+ if (a.kind === "instance" && b.kind === "instance") return a.location === b.location;
if (a.kind === "group" && b.kind === "group") return a.id === b.id;
return true;
}
diff --git a/src/renderer/src/features/profile/GroupsSection.tsx b/src/renderer/src/features/profile/GroupsSection.tsx
index e8e9b53..e2673b6 100644
--- a/src/renderer/src/features/profile/GroupsSection.tsx
+++ b/src/renderer/src/features/profile/GroupsSection.tsx
@@ -1,6 +1,7 @@
import { Star, Users } from "lucide-react";
import type { Group } from "../../../../shared/types/group";
import { Avatar, CollapsibleCard, SkeletonGrid } from "../../components/ui";
+import { useT } from "../../lib/i18n";
import { useNav } from "../navigation/NavContext";
import { useUserGroups } from "./useUserGroups";
@@ -41,6 +42,7 @@ export function GroupsSection({ userId }: { userId: string }) {
}
function FeaturedGroup({ group }: { group: Group }) {
+ const t = useT();
const { openGroup } = useNav();
return (
- Featured group
+ {t("profile:groups.featured")}
{group.name}
@@ -106,8 +108,9 @@ function GroupMeta({ group }: { group: Group }) {
}
function Wrap({ count, children }: { count: number | string; children: React.ReactNode }) {
+ const t = useT();
return (
-
+
{children}
);
diff --git a/src/renderer/src/features/profile/LocationSection.tsx b/src/renderer/src/features/profile/LocationSection.tsx
index 59e91ce..8cdc347 100644
--- a/src/renderer/src/features/profile/LocationSection.tsx
+++ b/src/renderer/src/features/profile/LocationSection.tsx
@@ -5,10 +5,12 @@ import { useWorld } from "../../store/worlds";
import { useNav } from "../navigation/NavContext";
import { api } from "../../lib/api";
import { useAsync } from "../../lib/useAsync";
-import { locationLabel, regionLabels, regionFlag } from "../../lib/vrchat";
+import { useT } from "../../lib/i18n";
+import { locationLabel, regionLabel, regionFlag } from "../../lib/vrchat";
export function LocationSection({ location }: { location?: Location }) {
- const { openWorld } = useNav();
+ const t = useT();
+ const { openInstance } = useNav();
const parsed = parseLocation(location);
const world = useWorld(parsed?.worldId);
const instance = useAsync(
@@ -33,14 +35,12 @@ export function LocationSection({ location }: { location?: Location }) {
if (parsed && world) {
const img = world.thumbnailImageUrl || world.imageUrl;
- const region = parsed.region
- ? (regionLabels[parsed.region] ?? parsed.region.toUpperCase())
- : null;
+ const region = regionLabel(t, parsed.region);
const flag = regionFlag(parsed.region);
return (