integrations keyed, and the Google guide's foreign labels marked (163 -> 96)
Applies the rule added to docs/localization.md in 91b9bec9: a label quoted from
another product's interface is an identifier, so the prose around it is translated
and the label itself is left in English and NOT keyed.
Gives that decision a name in the code. <Untranslated reason="foreign-ui-label">
states it at each of the ~30 call sites, where whoever next edits the sentence will
see it, and gives the scanner something exact to skip -- without which labels that
MUST stay English count as debt forever and the area can never be held at zero. That
is the same trap as counting new Error() messages. `reason` is a closed union so a
second excuse has to be argued for rather than smuggled in as free text.
The sentences needed rephrasing, not just splitting. "Go to the [New Project] page."
decomposes into "Go to the" + link + "page." -- and the Portuguese value of that
second key is "." A key that holds a full stop is the fragmentation smell, so the
English now puts the label at a clause boundary ("Open the project creation page:"
then the link), which reads naturally in both languages and needs no node-level
interpolation. Worth knowing for phase 3: sentences with an inline link are the one
shape this catalog format cannot express, and rephrasing is the cheap way out.
Scanner: the existing SCREAMING_CONSTANT rule now allows . and - so Google's
client-secret prefix example GOCSPX-... falls under it. Same rule, wider class.
This commit is contained in:
+13
-11
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
|
||||
type GoogleStatus = {
|
||||
connected: boolean;
|
||||
@@ -11,6 +12,7 @@ type GoogleStatus = {
|
||||
};
|
||||
|
||||
export const GoogleAccount = () => {
|
||||
const { t } = useTranslation();
|
||||
const client = useClient();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [status, setStatus] = useState<GoogleStatus>({
|
||||
@@ -30,9 +32,9 @@ export const GoogleAccount = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const result = params.get('google');
|
||||
if (result === 'success') {
|
||||
toast.success('Google account connected');
|
||||
toast.success(t('settings.googleAccount.connectedToast'));
|
||||
} else if (result === 'error') {
|
||||
toast.error('Failed to connect Google account');
|
||||
toast.error(t('settings.googleAccount.connectFailed'));
|
||||
}
|
||||
if (result) {
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
@@ -51,9 +53,9 @@ export const GoogleAccount = () => {
|
||||
try {
|
||||
await client.delete('/integrations/google/connection');
|
||||
setStatus({ ...status, connected: false, email: null });
|
||||
toast.success('Google account disconnected');
|
||||
toast.success(t('settings.googleAccount.disconnectedToast'));
|
||||
} catch {
|
||||
toast.error('Failed to disconnect Google account');
|
||||
toast.error(t('settings.googleAccount.disconnectFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,17 +65,17 @@ export const GoogleAccount = () => {
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-duck-dark dark:text-foreground">Google Account</p>
|
||||
<p className="text-xs text-duck-dark/50 dark:text-foreground/50 mt-1">
|
||||
Connect your Google account for Calendar, Contacts, and other Google services.
|
||||
</p>
|
||||
<p className="text-sm font-medium text-duck-dark dark:text-foreground">{t('settings.googleAccount.title')}</p>
|
||||
<p className="text-xs text-duck-dark/50 dark:text-foreground/50 mt-1">{t('settings.googleAccount.hint')}</p>
|
||||
</div>
|
||||
{status.connected ? (
|
||||
<>
|
||||
<div className="flex items-center gap-3 rounded-lg border border-duck-dark/10 dark:border-foreground/10 p-4">
|
||||
<div className="h-2.5 w-2.5 rounded-full bg-success shrink-0" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium text-duck-dark dark:text-foreground">Connected</p>
|
||||
<p className="text-sm font-medium text-duck-dark dark:text-foreground">
|
||||
{t('settings.googleAccount.connected')}
|
||||
</p>
|
||||
<p className="text-xs text-duck-dark/50 dark:text-foreground/50 truncate">{status.email}</p>
|
||||
</div>
|
||||
{status.picture && (
|
||||
@@ -81,7 +83,7 @@ export const GoogleAccount = () => {
|
||||
)}
|
||||
</div>
|
||||
<Button type="button" variant="outline" onClick={handleDisconnect} className="w-full h-11 cursor-pointer">
|
||||
Disconnect
|
||||
{t('settings.googleAccount.disconnect')}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
@@ -90,7 +92,7 @@ export const GoogleAccount = () => {
|
||||
onClick={handleConnect}
|
||||
className="w-full h-11 bg-duck-yellow hover:bg-duck-yellow/90 text-duck-teal font-bold transition-all duration-200 hover:scale-105 cursor-pointer"
|
||||
>
|
||||
Connect Google Account
|
||||
{t('settings.googleAccount.connect')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user