add admin/license i18n

This commit is contained in:
Lokowitz 2025-05-06 09:41:44 +00:00
parent 4dd9f4736d
commit 1e72b0f854
4 changed files with 124 additions and 98 deletions

View file

@ -297,5 +297,58 @@
"userDeleteServer": "Delete User from Server",
"userMessageRemove": "The user will be removed from all organizations and be completely removed from the server.",
"userMessageConfirm": "To confirm, please type the name of the user below.",
"userQuestionRemove": "Are you sure you want to permanently delete {selectedUser} from the server?"
"userQuestionRemove": "Are you sure you want to permanently delete {selectedUser} from the server?",
"licenseKey": "License Key",
"valid": "Valid",
"numberOfSites": "Number of Sites",
"licenseKeySearch": "Search license keys...",
"licenseKeyAdd": "Add License Key",
"type": "Type",
"licenseKeyRequired": "License key is required",
"licenseTermsAgree": "You must agree to the license terms",
"licenseErrorKeyLoad": "Failed to load license keys",
"licenseErrorKeyLoadDescription": "An error occurred loading license keys.",
"licenseErrorKeyDelete": "Failed to delete license key",
"licenseErrorKeyDeleteDescription": "An error occurred deleting license key.",
"licenseKeyDeleted": "License key deleted",
"licenseKeyDeletedDescription": "The license key has been deleted.",
"licenseErrorKeyActivate": "Failed to activate license key",
"licenseErrorKeyActivateDescription": "An error occurred while activating the license key.",
"licenseKeyActivated": "License key activated",
"licenseKeyActivatedDescription": "The license key has been successfully activated.",
"licenseErrorKeyRecheck": "Failed to recheck license keys",
"licenseErrorKeyRecheckDescription": "An error occurred rechecking license keys.",
"licenseErrorKeyRechecked": "License keys rechecked",
"licenseErrorKeyRecheckedDescription": "All license keys have been rechecked",
"licenseActivateKey": "Activate License Key",
"licenseActivateKeyDescription": "Enter a license key to activate it.",
"licenseActivate": "Activate License",
"licenseAgreement": "By checking this box, you confirm that you have read and agree to the license terms corresponding to the tier associated with your license key.",
"fossorialLicense": "View Fossorial Commercial License & Subscription Terms",
"licenseMessageRemove": "This will remove the license key and all associated permissions granted by it.",
"licenseMessageConfirm": "To confirm, please type the license key below.",
"licenseQuestionRemove": "Are you sure you want to delete the license key {selectedKey} ?",
"licenseKeyDelete": "Delete License Key",
"licenseKeyDeleteConfirm": "Confirm Delete License Key",
"licenseTitle": "Manage License Status",
"licenseTitleDescription": "View and manage license keys in the system",
"licenseHost": "Host License",
"licenseHostDescription": "Manage the main license key for the host.",
"notLicensed": "Not Licensed",
"hostId": "Host ID",
"licenseReckeckAll": "Recheck All Keys",
"licenseSiteUsage": "Sites Usage",
"licenseSiteUsageDecsription": "View the number of sites using this license.",
"licenseNoSiteLimit": "There is no limit on the number of sites using an unlicensed host.",
"licensePurchase": "Purchase License",
"licensePurchaseSites": "Purchase Additional Sites",
"licenseSitesUsedMax": "{usedSites} of {maxSites} sites used",
"licenseSitesUsed": "{count, plural, =0 {# sites} =1 {# site} other {# sites}} in system.",
"licensePurchaseDescription": "Choose how many sites you want to {selectedMode, select, license {purchase a license for. You can always add more sites later.} other {add to your existing license.}}",
"licenseFee": "License fee",
"licensePriceSite": "Price per site",
"total": "Total",
"licenseContinuePayment": "Continue to Payment",
"pricingPage": "pricing page",
"licensePricingPage": "For the most up-to-date pricing and discounts, please visit the "
}

View file

@ -13,6 +13,7 @@ import { LicenseKeyCache } from "@server/license/license";
import { ArrowUpDown } from "lucide-react";
import moment from "moment";
import CopyToClipboard from "@app/components/CopyToClipboard";
import { useTranslations } from 'next-intl';
type LicenseKeysDataTableProps = {
licenseKeys: LicenseKeyCache[];
@ -32,6 +33,9 @@ export function LicenseKeysDataTable({
onDelete,
onCreate
}: LicenseKeysDataTableProps) {
const t = useTranslations();
const columns: ColumnDef<LicenseKeyCache>[] = [
{
accessorKey: "licenseKey",
@ -43,7 +47,7 @@ export function LicenseKeysDataTable({
column.toggleSorting(column.getIsSorted() === "asc")
}
>
License Key
{t('licenseKey')}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
@ -68,7 +72,7 @@ export function LicenseKeysDataTable({
column.toggleSorting(column.getIsSorted() === "asc")
}
>
Valid
{t('valid')}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
@ -87,7 +91,7 @@ export function LicenseKeysDataTable({
column.toggleSorting(column.getIsSorted() === "asc")
}
>
Type
{t('type')}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
@ -112,7 +116,7 @@ export function LicenseKeysDataTable({
column.toggleSorting(column.getIsSorted() === "asc")
}
>
Number of Sites
{t('numberOfSites')}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
@ -126,7 +130,7 @@ export function LicenseKeysDataTable({
variant="outlinePrimary"
onClick={() => onDelete(row.original)}
>
Delete
{t('delete')}
</Button>
</div>
)
@ -138,10 +142,10 @@ export function LicenseKeysDataTable({
columns={columns}
data={licenseKeys}
title="License Keys"
searchPlaceholder="Search license keys..."
searchPlaceholder={t('licenseKeySearch')}
searchColumn="licenseKey"
onAdd={onCreate}
addButtonText="Add License Key"
addButtonText={t('licenseKeyAdd')}
/>
);
}

View file

@ -16,6 +16,7 @@ import {
CredenzaHeader,
CredenzaTitle
} from "@app/components/Credenza";
import { useTranslations } from 'next-intl';
type SitePriceCalculatorProps = {
isOpen: boolean;
@ -60,27 +61,26 @@ export function SitePriceCalculator({
? licenseFlatRate + siteCount * pricePerSite
: siteCount * pricePerSite;
const t = useTranslations();
return (
<Credenza open={isOpen} onOpenChange={onOpenChange}>
<CredenzaContent>
<CredenzaHeader>
<CredenzaTitle>
{mode === "license"
? "Purchase License"
: "Purchase Additional Sites"}
? t('licensePurchase')
: t('licensePurchaseSites')}
</CredenzaTitle>
<CredenzaDescription>
Choose how many sites you want to{" "}
{mode === "license"
? "purchase a license for. You can always add more sites later."
: "add to your existing license."}
{t('licensePurchaseDescription', {selectedMode: mode})}
</CredenzaDescription>
</CredenzaHeader>
<CredenzaBody>
<div className="space-y-6">
<div className="flex flex-col items-center space-y-4">
<div className="text-sm font-medium text-muted-foreground">
Number of Sites
{t('numberOfSites')}
</div>
<div className="flex items-center space-x-4">
<Button
@ -110,7 +110,7 @@ export function SitePriceCalculator({
{mode === "license" && (
<div className="flex justify-between items-center">
<span className="text-sm font-medium">
License fee:
{t('licenseFee')}:
</span>
<span className="font-medium">
${licenseFlatRate.toFixed(2)}
@ -119,7 +119,7 @@ export function SitePriceCalculator({
)}
<div className="flex justify-between items-center mt-2">
<span className="text-sm font-medium">
Price per site:
{t('licensePriceSite')}:
</span>
<span className="font-medium">
${pricePerSite.toFixed(2)}
@ -127,25 +127,24 @@ export function SitePriceCalculator({
</div>
<div className="flex justify-between items-center mt-2">
<span className="text-sm font-medium">
Number of sites:
{t('numberOfSites')}:
</span>
<span className="font-medium">{siteCount}</span>
</div>
<div className="flex justify-between items-center mt-4 text-lg font-bold">
<span>Total:</span>
<span>{t('total')}:</span>
<span>${totalCost.toFixed(2)} / mo</span>
</div>
<p className="text-muted-foreground text-sm mt-2 text-center">
For the most up-to-date pricing and discounts,
please visit the{" "}
{t('licensePricingPage')}
<a
href="https://docs.fossorial.io/pricing"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
pricing page
{t('pricingPage')}
</a>
.
</p>
@ -154,10 +153,10 @@ export function SitePriceCalculator({
</CredenzaBody>
<CredenzaFooter>
<CredenzaClose asChild>
<Button variant="outline">Cancel</Button>
<Button variant="outline">{t('cancel')}</Button>
</CredenzaClose>
<Button onClick={continueToPayment}>
Continue to Payment
{t('licenseContinuePayment')}
</Button>
</CredenzaFooter>
</CredenzaContent>

View file

@ -57,6 +57,7 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { SitePriceCalculator } from "./components/SitePriceCalculator";
import Link from "next/link";
import { Checkbox } from "@app/components/ui/checkbox";
import { useTranslations } from 'next-intl';
const formSchema = z.object({
licenseKey: z
@ -77,6 +78,7 @@ function obfuscateLicenseKey(key: string): string {
export default function LicensePage() {
const api = createApiClient(useEnvContext());
const t = useTranslations();
const [rows, setRows] = useState<LicenseKeyCache[]>([]);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
@ -129,11 +131,8 @@ export default function LicensePage() {
}
} catch (e) {
toast({
title: "Failed to load license keys",
description: formatAxiosError(
e,
"An error occurred loading license keys"
)
title: t('licenseErrorKeyLoad'),
description: formatAxiosError(e, t('licenseErrorKeyLoadDescription'))
});
}
}
@ -148,17 +147,14 @@ export default function LicensePage() {
}
await loadLicenseKeys();
toast({
title: "License key deleted",
description: "The license key has been deleted"
title: t('licenseKeyDeleted'),
description: t('licenseKeyDeletedDescription')
});
setIsDeleteModalOpen(false);
} catch (e) {
toast({
title: "Failed to delete license key",
description: formatAxiosError(
e,
"An error occurred deleting license key"
)
title: t('licenseErrorKeyDelete'),
description: formatAxiosError(e, t('licenseErrorKeyDeleteDescription'))
});
} finally {
setIsDeletingLicense(false);
@ -174,16 +170,13 @@ export default function LicensePage() {
}
await loadLicenseKeys();
toast({
title: "License keys rechecked",
description: "All license keys have been rechecked"
title: t('licenseErrorKeyRechecked'),
description: t('licenseErrorKeyRecheckedDescription')
});
} catch (e) {
toast({
title: "Failed to recheck license keys",
description: formatAxiosError(
e,
"An error occurred rechecking license keys"
)
title: t('licenseErrorKeyRecheck'),
description: formatAxiosError(e, t('licenseErrorKeyRecheckDescription'))
});
} finally {
setIsRecheckingLicense(false);
@ -201,8 +194,8 @@ export default function LicensePage() {
}
toast({
title: "License key activated",
description: "The license key has been successfully activated."
title: t('licenseKeyActivated'),
description: t('licenseKeyActivatedDescription')
});
setIsCreateModalOpen(false);
@ -211,11 +204,8 @@ export default function LicensePage() {
} catch (e) {
toast({
variant: "destructive",
title: "Failed to activate license key",
description: formatAxiosError(
e,
"An error occurred while activating the license key."
)
title: t('licenseErrorKeyActivate'),
description: formatAxiosError(e, t('licenseErrorKeyActivateDescription'))
});
} finally {
setIsActivatingLicense(false);
@ -245,9 +235,9 @@ export default function LicensePage() {
>
<CredenzaContent>
<CredenzaHeader>
<CredenzaTitle>Activate License Key</CredenzaTitle>
<CredenzaTitle>{t('licenseActivateKey')}</CredenzaTitle>
<CredenzaDescription>
Enter a license key to activate it.
{t('licenseActivateKeyDescription')}
</CredenzaDescription>
</CredenzaHeader>
<CredenzaBody>
@ -262,7 +252,7 @@ export default function LicensePage() {
name="licenseKey"
render={({ field }) => (
<FormItem>
<FormLabel>License Key</FormLabel>
<FormLabel>{t('licenseKey')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@ -285,12 +275,7 @@ export default function LicensePage() {
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>
By checking this box, you
confirm that you have read
and agree to the license
terms corresponding to the
tier associated with your
license key.
{t('licenseAgreement')}
<br />
<Link
href="https://fossorial.io/license.html"
@ -298,9 +283,7 @@ export default function LicensePage() {
rel="noopener noreferrer"
className="text-primary hover:underline"
>
View Fossorial
Commercial License &
Subscription Terms
{t('fossorialLicense')}
</Link>
</FormLabel>
<FormMessage />
@ -313,7 +296,7 @@ export default function LicensePage() {
</CredenzaBody>
<CredenzaFooter>
<CredenzaClose asChild>
<Button variant="outline">Close</Button>
<Button variant="outline">{t('close')}</Button>
</CredenzaClose>
<Button
type="submit"
@ -321,7 +304,7 @@ export default function LicensePage() {
loading={isActivatingLicense}
disabled={isActivatingLicense}
>
Activate License
{t('licenseActivate')}
</Button>
</CredenzaFooter>
</CredenzaContent>
@ -336,47 +319,40 @@ export default function LicensePage() {
}}
dialog={
<div className="space-y-4">
<p>
Are you sure you want to delete the license key{" "}
<b>
{obfuscateLicenseKey(
selectedLicenseKey.licenseKey
)}
</b>
?
<p>
{t('licenseQuestionRemove', {selectedKey: obfuscateLicenseKey(selectedLicenseKey.licenseKey)})}
</p>
<p>
<b>
This will remove the license key and all
associated permissions granted by it.
{t('licenseMessageRemove')}
</b>
</p>
<p>
To confirm, please type the license key below.
{t('licenseMessageConfirm')}
</p>
</div>
}
buttonText="Confirm Delete License Key"
buttonText={t('licenseKeyDeleteConfirm')}
onConfirm={async () =>
deleteLicenseKey(selectedLicenseKey.licenseKeyEncrypted)
}
string={selectedLicenseKey.licenseKey}
title="Delete License Key"
title={t('licenseKeyDelete')}
/>
)}
<SettingsSectionTitle
title="Manage License Status"
description="View and manage license keys in the system"
title={t('licenseTitle')}
description={t('licenseTitleDescription')}
/>
<SettingsContainer>
<SettingsSectionGrid cols={2}>
<SettingsSection>
<SettingsSectionHeader>
<SSTitle>Host License</SSTitle>
<SSTitle>{t('licenseHost')}</SSTitle>
<SettingsSectionDescription>
Manage the main license key for the host.
{t('licenseHostDescription')}
</SettingsSectionDescription>
</SettingsSectionHeader>
<div className="space-y-4">
@ -397,7 +373,7 @@ export default function LicensePage() {
) : (
<div className="space-y-2">
<div className="text-2xl">
Not Licensed
{t('notLicensed')}
</div>
</div>
)}
@ -405,7 +381,7 @@ export default function LicensePage() {
{licenseStatus?.hostId && (
<div className="space-y-2">
<div className="text-sm font-medium">
Host ID
{t('hostId')}
</div>
<CopyTextBox text={licenseStatus.hostId} />
</div>
@ -413,7 +389,7 @@ export default function LicensePage() {
{hostLicense && (
<div className="space-y-2">
<div className="text-sm font-medium">
License Key
{t('licenseKey')}
</div>
<CopyTextBox
text={hostLicense}
@ -431,39 +407,33 @@ export default function LicensePage() {
disabled={isRecheckingLicense}
loading={isRecheckingLicense}
>
Recheck All Keys
{t('licenseReckeckAll')}
</Button>
</SettingsSectionFooter>
</SettingsSection>
<SettingsSection>
<SettingsSectionHeader>
<SSTitle>Sites Usage</SSTitle>
<SSTitle>{t('licenseSiteUsage')}</SSTitle>
<SettingsSectionDescription>
View the number of sites using this license.
{t('licenseSiteUsageDecsription')}
</SettingsSectionDescription>
</SettingsSectionHeader>
<div className="space-y-4">
<div className="space-y-2">
<div className="text-2xl">
{licenseStatus?.usedSites || 0}{" "}
{licenseStatus?.usedSites === 1
? "site"
: "sites"}{" "}
in system
{t('licenseSitesUsed', {count: licenseStatus?.usedSites || 0})}
</div>
</div>
{!licenseStatus?.isHostLicensed && (
<p className="text-sm text-muted-foreground">
There is no limit on the number of sites
using an unlicensed host.
{t('licenseNoSiteLimit')}
</p>
)}
{licenseStatus?.maxSites && (
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">
{licenseStatus.usedSites || 0} of{" "}
{licenseStatus.maxSites} sites used
{t('licenseSitesUsedMax', {usedSites: licenseStatus.usedSites || 0, maxSites: licenseStatus.maxSites})}
</span>
<span className="text-muted-foreground">
{Math.round(
@ -495,7 +465,7 @@ export default function LicensePage() {
setIsPurchaseModalOpen(true);
}}
>
Purchase License
{t('licensePurchase')}
</Button>
</>
) : (
@ -507,7 +477,7 @@ export default function LicensePage() {
setIsPurchaseModalOpen(true);
}}
>
Purchase Additional Sites
{t('licensePurchaseSites')}
</Button>
</>
)}