1
0
forked from baron/baron-sso

Merge pull request 'feature/ui-refactor' (#339) from feature/ui-refactor into dev

Reviewed-on: baron/baron-sso#339
This commit is contained in:
2026-02-25 17:12:59 +09:00
12 changed files with 887 additions and 380 deletions

View File

@@ -197,7 +197,20 @@ function AppLayout() {
))} ))}
</div> </div>
</nav> </nav>
<div className="hidden space-y-2 px-5 pb-6 text-xs text-[var(--color-muted)] md:block"> </div>
<div>
<div className="px-3 pt-4 border-t border-border/50">
<button
type="button"
onClick={handleLogout}
className="w-full flex items-center gap-3 rounded-xl px-3 py-3 text-sm transition text-muted-foreground hover:bg-destructive/10 hover:text-destructive"
>
<LogOut size={18} />
<span>{t("ui.dev.nav.logout", "Logout")}</span>
</button>
</div>
<div className="hidden space-y-2 px-5 pb-6 pt-2 text-xs text-[var(--color-muted)] md:block">
<p>{t("msg.dev.sidebar.notice", "개발자 전용 콘솔입니다.")}</p> <p>{t("msg.dev.sidebar.notice", "개발자 전용 콘솔입니다.")}</p>
<p> <p>
{t( {t(
@@ -207,17 +220,6 @@ function AppLayout() {
</p> </p>
</div> </div>
</div> </div>
<div className="px-2 pb-6 md:px-3">
<button
type="button"
onClick={handleLogout}
className="flex w-full items-center gap-3 rounded-xl px-3 py-3 text-sm text-muted-foreground transition hover:bg-muted/10 hover:text-foreground"
>
<LogOut size={18} />
<span>{t("ui.dev.nav.logout", "Logout")}</span>
</button>
</div>
</aside> </aside>
<div className="relative"> <div className="relative">

View File

@@ -1,6 +1,6 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { AxiosError } from "axios"; import type { AxiosError } from "axios";
import { Plus, Shield, Sparkles, Trash2, Upload } from "lucide-react"; import { Plus, Save, Shield, Sparkles, Trash2, Upload } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Link, useNavigate, useParams } from "react-router-dom"; import { Link, useNavigate, useParams } from "react-router-dom";
import { Badge } from "../../components/ui/badge"; import { Badge } from "../../components/ui/badge";
@@ -16,6 +16,7 @@ import { Input } from "../../components/ui/input";
import { Label } from "../../components/ui/label"; import { Label } from "../../components/ui/label";
import { Switch } from "../../components/ui/switch"; import { Switch } from "../../components/ui/switch";
import { Textarea } from "../../components/ui/textarea"; import { Textarea } from "../../components/ui/textarea";
import { toast } from "../../components/ui/use-toast";
import { import {
createClient, createClient,
deleteClient, deleteClient,
@@ -128,6 +129,21 @@ function ClientGeneralPage() {
setScopes(scopes.filter((s) => s.id !== id)); setScopes(scopes.filter((s) => s.id !== id));
}; };
const handleStatusChange = (nextStatus: ClientStatus) => {
setStatus(nextStatus);
const statusLabel =
nextStatus === "active"
? t("ui.common.status.active", "Active")
: t("ui.common.status.inactive", "Inactive");
toast(
t(
"msg.dev.clients.general.status_changed",
"상태가 {{status}}로 변경되었습니다.",
{ status: statusLabel },
),
);
};
const mutation = useMutation({ const mutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
const scopeNames = scopes.map((scope) => scope.name).filter(Boolean); const scopeNames = scopes.map((scope) => scope.name).filter(Boolean);
@@ -204,7 +220,7 @@ function ClientGeneralPage() {
window.confirm( window.confirm(
t( t(
"msg.dev.clients.delete_confirm", "msg.dev.clients.delete_confirm",
"정말로 이 앱을 삭제하시습니까? 이 작업은 되돌릴 수 없습니다.", "정말로 이 앱을 삭제하시습니까? 이 작업은 되돌릴 수 없습니다.",
), ),
) )
) { ) {
@@ -309,33 +325,6 @@ function ClientGeneralPage() {
)} )}
</CardDescription> </CardDescription>
</div> </div>
{!isCreate && (
<div className="flex flex-col items-end gap-2">
<Label className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
{t("ui.dev.clients.table.status", "상태")}
</Label>
<div className="flex items-center gap-3">
<Switch
checked={status === "active"}
onCheckedChange={(checked) =>
setStatus(checked ? "active" : "inactive")
}
/>
<span
className={cn(
"text-sm font-medium",
status === "active"
? "text-emerald-400"
: "text-muted-foreground",
)}
>
{status === "active"
? t("ui.common.status.active", "활성")
: t("ui.common.status.inactive", "비활성")}
</span>
</div>
</div>
)}
</div> </div>
<div className="grid gap-8 md:grid-cols-2"> <div className="grid gap-8 md:grid-cols-2">
<div className="space-y-5"> <div className="space-y-5">
@@ -371,40 +360,66 @@ function ClientGeneralPage() {
/> />
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="space-y-5">
<Label className="text-sm font-semibold"> <div className="space-y-2">
{t("ui.dev.clients.general.identity.logo", "App Logo URL")} <Label className="text-sm font-semibold">
</Label> {t("ui.dev.clients.general.identity.logo", "App Logo URL")}
<div className="flex gap-4"> </Label>
<div className="flex-1 space-y-2"> <div className="flex gap-4">
<Input <div className="flex-1 space-y-2">
value={logoUrl} <Input
onChange={(e) => setLogoUrl(e.target.value)} value={logoUrl}
placeholder={t( onChange={(e) => setLogoUrl(e.target.value)}
"ui.dev.clients.general.identity.logo_placeholder", placeholder={t(
"https://example.com/logo.png", "ui.dev.clients.general.identity.logo_placeholder",
)} "https://example.com/logo.png",
/>
<p className="text-xs text-muted-foreground">
{t(
"msg.dev.clients.general.identity.logo_help",
"인증 화면에 표시될 PNG/SVG URL입니다.",
)}
</p>
</div>
<div className="flex h-20 w-20 items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted/40 shrink-0">
{logoUrl ? (
<img
src={logoUrl}
alt={t(
"ui.dev.clients.general.identity.logo_preview",
"Logo Preview",
)} )}
className="h-full w-full object-contain"
/> />
) : ( <p className="text-xs text-muted-foreground">
<Upload className="h-5 w-5 text-muted-foreground" /> {t(
)} "msg.dev.clients.general.identity.logo_help",
"인증 화면에 표시될 PNG/SVG URL입니다.",
)}
</p>
</div>
<div className="flex h-20 w-20 items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted/40 shrink-0">
{logoUrl ? (
<img
src={logoUrl}
alt={t(
"ui.dev.clients.general.identity.logo_preview",
"Logo Preview",
)}
className="h-full w-full object-contain"
/>
) : (
<Upload className="h-5 w-5 text-muted-foreground" />
)}
</div>
</div>
</div>
<div className="space-y-2">
<Label className="text-sm font-semibold">
{t("ui.dev.clients.table.status", "상태")}
</Label>
<div className="flex gap-2">
<Button
type="button"
size="sm"
variant={status === "active" ? "default" : "outline"}
onClick={() => handleStatusChange("active")}
>
{t("ui.common.status.active", "활성")}
</Button>
<Button
type="button"
size="sm"
variant={status === "inactive" ? "default" : "outline"}
onClick={() => handleStatusChange("inactive")}
>
{t("ui.common.status.inactive", "비활성")}
</Button>
</div> </div>
</div> </div>
</div> </div>
@@ -658,20 +673,30 @@ function ClientGeneralPage() {
</Button> </Button>
)} )}
</div> </div>
<div className="flex items-center gap-3"> <div className="flex flex-col-reverse sm:flex-row sm:justify-end gap-2">
<Button variant="outline" onClick={() => navigate("/clients")}> <Button variant="outline" onClick={() => navigate("/clients")}>
{t("ui.common.cancel", "취소")} {t("ui.common.cancel", "취소")}
</Button> </Button>
<Button <Button
onClick={() => mutation.mutate()} onClick={() => mutation.mutate()}
disabled={mutation.isPending} disabled={
className="px-8 shadow-lg shadow-primary/20" mutation.isPending ||
isLoading ||
name.trim() === "" ||
(isCreate && redirectUris.trim() === "")
}
className="shadow-lg shadow-primary/20"
> >
{mutation.isPending ? (
<div className="h-4 w-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2" />
) : (
<Save size={16} className="mr-2" />
)}
{mutation.isPending {mutation.isPending
? t("msg.common.saving", "저장 중...") ? t("msg.common.saving", "저장 중...")
: isCreate : isCreate
? t("ui.dev.clients.general.create", "클라이언트 생성") ? t("ui.dev.clients.general.create", "클라이언트 생성")
: t("ui.dev.clients.general.save", "설정 저장")} : t("ui.common.save", "저장")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -1,11 +1,30 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Plus, Trash2, Edit, Globe, Save } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { Button } from "../../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "../../../components/ui/card";
import { Input } from "../../../components/ui/input";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "../../../components/ui/table";
import { import {
createIdpConfigForClient, createIdpConfigForClient,
listIdpConfigsForClient, listIdpConfigsForClient,
} from "../../../lib/devApi"; } from "../../../lib/devApi";
import type { IdpConfig, IdpConfigCreateRequest } from "../../../lib/devApi"; import type { IdpConfig, IdpConfigCreateRequest } from "../../../lib/devApi";
import { t } from "../../../lib/i18n";
// Proper Modal Component with Form // Proper Modal Component with Form
const CreateIdpModal = ({ const CreateIdpModal = ({
@@ -37,12 +56,10 @@ const CreateIdpModal = ({
onClose(); onClose();
}, },
onError: (error) => { onError: (error) => {
// Basic error handling
alert(`Failed to create configuration: ${error.message}`); alert(`Failed to create configuration: ${error.message}`);
}, },
}); });
// 이 내용으로 교체해주세요
const handleChange = ( const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>, e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>,
) => { ) => {
@@ -61,104 +78,100 @@ const CreateIdpModal = ({
if (!isOpen) return null; if (!isOpen) return null;
return ( return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 backdrop-blur-sm">
<div className="bg-white p-6 rounded-lg shadow-xl w-full max-w-lg"> <Card className="w-full max-w-lg shadow-2xl animate-in zoom-in-95 duration-200">
<h2 className="text-xl font-bold mb-4">Add New IdP Configuration</h2> <CardHeader>
<form onSubmit={handleSubmit}> <CardTitle>
{/* Display Name */} {t("ui.dev.clients.federation.add_title", "Add Identity Provider")}
<div className="mb-4"> </CardTitle>
<label className="block text-gray-700 text-sm font-bold mb-2"> <CardDescription>
Display Name {t(
</label> "msg.dev.clients.federation.add_subtitle",
<input "Connect an external OIDC provider.",
type="text" )}
name="display_name" </CardDescription>
value={formData.display_name} </CardHeader>
onChange={handleChange} <CardContent>
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" <form onSubmit={handleSubmit} className="space-y-4">
required <div className="space-y-2">
/> <label className="text-sm font-semibold">Display Name</label>
</div> <Input
name="display_name"
value={formData.display_name}
onChange={handleChange}
placeholder="e.g. Google Workspace"
required
/>
</div>
{/* Issuer URL */} <div className="space-y-2">
<div className="mb-4"> <label className="text-sm font-semibold">Issuer URL</label>
<label className="block text-gray-700 text-sm font-bold mb-2"> <Input
Issuer URL type="url"
</label> name="issuer_url"
<input value={formData.issuer_url}
type="url" onChange={handleChange}
name="issuer_url" placeholder="https://accounts.google.com"
value={formData.issuer_url} required
onChange={handleChange} />
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" </div>
placeholder="https://accounts.google.com"
required
/>
</div>
{/* Client ID */} <div className="grid grid-cols-2 gap-4">
<div className="mb-4"> <div className="space-y-2">
<label className="block text-gray-700 text-sm font-bold mb-2"> <label className="text-sm font-semibold">Client ID</label>
Client ID <Input
</label> name="oidc_client_id"
<input value={formData.oidc_client_id}
type="text" onChange={handleChange}
name="oidc_client_id" required
value={formData.oidc_client_id} />
onChange={handleChange} </div>
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" <div className="space-y-2">
required <label className="text-sm font-semibold">Client Secret</label>
/> <Input
</div> type="password"
name="oidc_client_secret"
value={formData.oidc_client_secret}
onChange={handleChange}
required
/>
</div>
</div>
{/* Client Secret */} <div className="space-y-2">
<div className="mb-4"> <label className="text-sm font-semibold">Scopes</label>
<label className="block text-gray-700 text-sm font-bold mb-2"> <Input
Client Secret name="scopes"
</label> value={formData.scopes}
<input onChange={handleChange}
type="password" />
name="oidc_client_secret" </div>
value={formData.oidc_client_secret}
onChange={handleChange}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
required
/>
</div>
{/* Scopes */} <div className="flex flex-col-reverse sm:flex-row sm:justify-end gap-2 pt-4">
<div className="mb-4"> <Button type="button" variant="outline" onClick={onClose}>
<label className="block text-gray-700 text-sm font-bold mb-2"> {t("ui.common.cancel", "Cancel")}
Scopes </Button>
</label> <Button
<input type="submit"
type="text" disabled={
name="scopes" mutation.isPending ||
value={formData.scopes} formData.display_name.trim() === "" ||
onChange={handleChange} formData.issuer_url.trim() === ""
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" }
/> >
</div> {mutation.isPending ? (
<div className="h-4 w-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2" />
{/* Action Buttons */} ) : (
<div className="flex items-center justify-end"> <Save size={16} className="mr-2" />
<button )}
type="button" {mutation.isPending
onClick={onClose} ? t("msg.common.saving", "Saving...")
className="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded mr-2" : t("ui.common.save", "Save Configuration")}
> </Button>
Cancel </div>
</button> </form>
<button </CardContent>
type="submit" </Card>
disabled={mutation.isPending}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50"
>
{mutation.isPending ? "Saving..." : "Save Configuration"}
</button>
</div>
</form>
</div>
</div> </div>
); );
}; };
@@ -168,7 +181,11 @@ export function ClientFederationPage() {
const [isCreateModalOpen, setCreateModalOpen] = useState(false); const [isCreateModalOpen, setCreateModalOpen] = useState(false);
if (!clientId) { if (!clientId) {
return <div>Client ID is missing</div>; return (
<div className="p-8 text-center text-destructive">
Client ID is missing
</div>
);
} }
const { data, isLoading, error } = useQuery({ const { data, isLoading, error } = useQuery({
@@ -177,94 +194,113 @@ export function ClientFederationPage() {
}); });
return ( return (
<div className="p-4"> <div className="space-y-6 p-1">
<h1 className="text-2xl font-bold mb-4">Identity Federation Settings</h1> <header className="flex flex-wrap items-center justify-between gap-4">
<p className="mb-4 text-gray-600"> <div>
Manage external identity providers for this application. <h1 className="text-2xl font-bold flex items-center gap-2">
</p> <Globe className="h-6 w-6 text-primary" />
{t("ui.dev.clients.federation.title", "Identity Federation")}
</h1>
<p className="text-muted-foreground">
{t(
"msg.dev.clients.federation.subtitle",
"Manage external identity providers for this application.",
)}
</p>
</div>
<Button onClick={() => setCreateModalOpen(true)} className="gap-2">
<Plus className="h-4 w-4" />
{t("ui.dev.clients.federation.add_btn", "Add Provider")}
</Button>
</header>
<div className="mb-4"> <Card className="glass-panel">
<button <CardContent className="p-0">
type="button" <Table>
onClick={() => setCreateModalOpen(true)} <TableHeader>
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" <TableRow>
> <TableHead>Display Name</TableHead>
+ Add IdP Configuration <TableHead>Provider Type</TableHead>
</button> <TableHead>Status</TableHead>
</div> <TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={4} className="h-24 text-center">
{t("msg.common.loading", "Loading...")}
</TableCell>
</TableRow>
) : error ? (
<TableRow>
<TableCell
colSpan={4}
className="h-24 text-center text-destructive"
>
{(error as Error).message}
</TableCell>
</TableRow>
) : data?.length === 0 ? (
<TableRow>
<TableCell
colSpan={4}
className="h-24 text-center text-muted-foreground"
>
{t(
"msg.dev.clients.federation.empty",
"No IdP configurations found.",
)}
</TableCell>
</TableRow>
) : (
data?.map((config: IdpConfig) => (
<tr
key={config.id}
className="border-b transition-colors hover:bg-muted/50"
>
<TableCell className="font-medium">
{config.display_name}
</TableCell>
<TableCell>{config.provider_type.toUpperCase()}</TableCell>
<TableCell>
<span
className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${
config.status === "active"
? "bg-emerald-500/10 text-emerald-500"
: "bg-muted text-muted-foreground"
}`}
>
{config.status}
</span>
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
<Button variant="ghost" size="icon" className="h-8 w-8">
<Edit className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-destructive"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</tr>
))
)}
</TableBody>
</Table>
</CardContent>
</Card>
<CreateIdpModal <CreateIdpModal
isOpen={isCreateModalOpen} isOpen={isCreateModalOpen}
onClose={() => setCreateModalOpen(false)} onClose={() => setCreateModalOpen(false)}
clientId={clientId} clientId={clientId}
/> />
{isLoading && <div>Loading configurations...</div>}
{error && (
<div className="text-red-500">
Failed to load configurations: {error.message}
</div>
)}
{data && (
<div className="overflow-x-auto">
<table className="min-w-full bg-white">
<thead className="bg-gray-200">
<tr>
<th className="py-2 px-4 border-b">Display Name</th>
<th className="py-2 px-4 border-b">Provider Type</th>
<th className="py-2 px-4 border-b">Status</th>
<th className="py-2 px-4 border-b">Actions</th>
</tr>
</thead>
<tbody>
{data.length === 0 ? (
<tr>
<td colSpan={4} className="text-center py-4">
No IdP configurations found.
</td>
</tr>
) : (
data.map((config: IdpConfig) => (
<tr key={config.id}>
<td className="py-2 px-4 border-b">
{config.display_name}
</td>
<td className="py-2 px-4 border-b">
{config.provider_type.toUpperCase()}
</td>
<td className="py-2 px-4 border-b">
<span
className={`px-2 py-1 text-xs font-semibold rounded-full ${
config.status === "active"
? "bg-green-200 text-green-800"
: "bg-gray-200 text-gray-800"
}`}
>
{config.status}
</span>
</td>
<td className="py-2 px-4 border-b">
<button
type="button"
className="text-blue-500 hover:underline mr-2"
>
Edit
</button>
<button
type="button"
className="text-red-500 hover:underline"
>
Delete
</button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
)}
</div> </div>
); );
} }

View File

@@ -252,6 +252,12 @@ load_error = "Error loading client: {{error}}"
loading = "Loading client..." loading = "Loading client..."
saved = "Saved" saved = "Saved"
save_error = "Failed to save: {{error}}" save_error = "Failed to save: {{error}}"
status_changed = "Status changed to {{status}}."
[msg.dev.clients.federation]
subtitle = "Manage external identity providers for this application."
add_subtitle = "Connect an external OIDC provider."
empty = "No IdP configurations found."
[msg.dev.clients.general.identity] [msg.dev.clients.general.identity]
logo_help = "Logo Help" logo_help = "Logo Help"
@@ -1019,10 +1025,14 @@ settings = "Settings"
[ui.dev.clients.general] [ui.dev.clients.general]
create = "Create Application" create = "Create Application"
display_new = "Add Connected Application" display_new = "Add Connected Application"
save = "Settings Save"
title_create = "Create Client" title_create = "Create Client"
title_edit = "Client Settings" title_edit = "Client Settings"
[ui.dev.clients.federation]
title = "Identity Federation"
add_title = "Add Identity Provider"
add_btn = "Add Provider"
[ui.dev.clients.general.breadcrumb] [ui.dev.clients.general.breadcrumb]
section = "Applications" section = "Applications"

View File

@@ -252,6 +252,12 @@ load_error = "Error loading client: {{error}}"
loading = "Loading client..." loading = "Loading client..."
saved = "설정이 저장되었습니다." saved = "설정이 저장되었습니다."
save_error = "저장 실패: {{error}}" save_error = "저장 실패: {{error}}"
status_changed = "상태가 {{status}}로 변경되었습니다."
[msg.dev.clients.federation]
subtitle = "이 애플리케이션의 외부 IdP 설정을 관리합니다."
add_subtitle = "외부 OIDC 제공자를 연결합니다."
empty = "등록된 IdP 설정이 없습니다."
[msg.dev.clients.general.identity] [msg.dev.clients.general.identity]
logo_help = "인증 화면에 표시될 PNG/SVG URL입니다." logo_help = "인증 화면에 표시될 PNG/SVG URL입니다."
@@ -1019,10 +1025,14 @@ settings = "Settings"
[ui.dev.clients.general] [ui.dev.clients.general]
create = "앱 생성" create = "앱 생성"
display_new = "연동 앱 추가" display_new = "연동 앱 추가"
save = "설정 저장"
title_create = "Create Client" title_create = "Create Client"
title_edit = "Client Settings" title_edit = "Client Settings"
[ui.dev.clients.federation]
title = "Identity Federation"
add_title = "Add Identity Provider"
add_btn = "Add Provider"
[ui.dev.clients.general.breadcrumb] [ui.dev.clients.general.breadcrumb]
section = "Applications" section = "Applications"

View File

@@ -252,6 +252,12 @@ load_error = ""
loading = "" loading = ""
saved = "" saved = ""
save_error = "" save_error = ""
status_changed = ""
[msg.dev.clients.federation]
subtitle = ""
add_subtitle = ""
empty = ""
[msg.dev.clients.general.identity] [msg.dev.clients.general.identity]
logo_help = "" logo_help = ""
@@ -1031,10 +1037,14 @@ settings = ""
[ui.dev.clients.general] [ui.dev.clients.general]
create = "" create = ""
display_new = "" display_new = ""
save = ""
title_create = "" title_create = ""
title_edit = "" title_edit = ""
[ui.dev.clients.federation]
title = ""
add_title = ""
add_btn = ""
[ui.dev.clients.general.breadcrumb] [ui.dev.clients.general.breadcrumb]
section = "" section = ""

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -250,6 +250,12 @@ load_error = ""
loading = "" loading = ""
saved = "" saved = ""
save_error = "" save_error = ""
status_changed = ""
[msg.dev.clients.federation]
subtitle = ""
add_subtitle = ""
empty = ""
[msg.dev.clients.general.identity] [msg.dev.clients.general.identity]
logo_help = "" logo_help = ""
@@ -1030,10 +1036,14 @@ settings = ""
[ui.dev.clients.general] [ui.dev.clients.general]
create = "" create = ""
display_new = "" display_new = ""
save = ""
title_create = "" title_create = ""
title_edit = "" title_edit = ""
[ui.dev.clients.federation]
title = ""
add_title = ""
add_btn = ""
[ui.dev.clients.general.breadcrumb] [ui.dev.clients.general.breadcrumb]
section = "" section = ""

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long