@rebasepro/app 0.14.1-canary.g7e666eb → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/LoginView/LoginView.d.ts +12 -5
- package/dist/hooks/useTranslation.d.ts +1 -1
- package/dist/index.es.js +120 -16
- package/dist/index.es.js.map +1 -1
- package/package.json +7 -7
- package/src/components/LoginView/LoginView.tsx +111 -28
- package/src/hooks/useTranslation.ts +6 -1
- package/src/locales/de.ts +10 -0
- package/src/locales/en.ts +10 -0
- package/src/locales/es.ts +10 -0
- package/src/locales/fr.ts +10 -0
- package/src/locales/hi.ts +10 -0
- package/src/locales/it.ts +10 -0
- package/src/locales/pt.ts +10 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/app",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.14.1
|
|
4
|
+
"version": "0.14.1",
|
|
5
5
|
"description": "Rebase core — framework-agnostic runtime for data-driven admin panels",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
"magic-string": "^0.30.0",
|
|
53
53
|
"notistack": "^3.0.2",
|
|
54
54
|
"react-i18next": "^17.0.11",
|
|
55
|
-
"@rebasepro/admin-types": "0.14.1
|
|
56
|
-
"@rebasepro/
|
|
57
|
-
"@rebasepro/
|
|
58
|
-
"@rebasepro/
|
|
59
|
-
"@rebasepro/
|
|
60
|
-
"@rebasepro/
|
|
55
|
+
"@rebasepro/admin-types": "0.14.1",
|
|
56
|
+
"@rebasepro/types": "0.14.1",
|
|
57
|
+
"@rebasepro/forms": "0.14.1",
|
|
58
|
+
"@rebasepro/common": "0.14.1",
|
|
59
|
+
"@rebasepro/utils": "0.14.1",
|
|
60
|
+
"@rebasepro/ui": "0.14.1"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"react": ">=19.2.7",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { ReactNode, useEffect, useId, useRef, useState } from "react";
|
|
1
|
+
import React, { ReactNode, useCallback, useEffect, useId, useRef, useState } from "react";
|
|
2
2
|
|
|
3
3
|
/** Google Identity Services SDK — injected by the GIS <script> tag. */
|
|
4
4
|
declare global {
|
|
@@ -145,11 +145,18 @@ export interface LoginViewProps {
|
|
|
145
145
|
defaultPassword?: string;
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
|
-
* When set,
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
148
|
+
* When set, a "Join the newsletter" opt-in checkbox is offered. Called with
|
|
149
|
+
* the address that just authenticated — by any method — if the box was
|
|
150
|
+
* ticked. Wire it to whatever stores the subscription; failures are the
|
|
151
|
+
* callback's problem, the login flow never waits on it.
|
|
152
|
+
*
|
|
153
|
+
* The checkbox sits on the **provider screen**, beside the buttons that
|
|
154
|
+
* choose how to sign in, rather than on the credentials form. It is a
|
|
155
|
+
* decision about the visitor, not about the password: asking on the
|
|
156
|
+
* credentials form put it in front of only the people who got that far by
|
|
157
|
+
* typing an address, so anyone signing in with Google was never offered it
|
|
158
|
+
* at all. Bootstrap mode is the exception — there is no provider screen
|
|
159
|
+
* there, so the form keeps it.
|
|
153
160
|
*/
|
|
154
161
|
onNewsletterOptIn?: (email: string) => void;
|
|
155
162
|
}
|
|
@@ -225,10 +232,32 @@ export function LoginView({
|
|
|
225
232
|
const [mode, setMode] = useState<AuthMode>("buttons");
|
|
226
233
|
const [fadeIn, setFadeIn] = useState(false);
|
|
227
234
|
const [viewVisible, setViewVisible] = useState(true);
|
|
228
|
-
// Lives here, not in LoginForm: the
|
|
229
|
-
//
|
|
235
|
+
// Lives here, not in LoginForm: the box is ticked on the provider screen
|
|
236
|
+
// and read after a sign-in that happens one or two screens later, so it has
|
|
237
|
+
// to outlive both. The form also unmounts on every login↔register switch.
|
|
230
238
|
const [newsletterOptIn, setNewsletterOptIn] = useState(false);
|
|
231
239
|
|
|
240
|
+
// The controller as of the latest render. A provider callback resolves long
|
|
241
|
+
// after the click that started it, and the `authController` captured in that
|
|
242
|
+
// closure is the one from the render that mounted the button — which, for a
|
|
243
|
+
// controller replaced on each render, has no `user` on it yet.
|
|
244
|
+
const authControllerRef = useRef(authController);
|
|
245
|
+
authControllerRef.current = authController;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Subscribe the address that just authenticated, if the visitor asked for
|
|
249
|
+
* it. Every sign-in path calls this, which is the point: the checkbox sits
|
|
250
|
+
* beside the provider buttons, so it has to mean the same thing whichever
|
|
251
|
+
* one is pressed.
|
|
252
|
+
*
|
|
253
|
+
* Only ever from a resolution path — a ticked box on a *failed* attempt
|
|
254
|
+
* must not subscribe an address nobody proved they control.
|
|
255
|
+
*/
|
|
256
|
+
const subscribeIfOptedIn = useCallback((email?: string | null) => {
|
|
257
|
+
if (!newsletterOptIn || !onNewsletterOptIn || !email) return;
|
|
258
|
+
onNewsletterOptIn(email);
|
|
259
|
+
}, [newsletterOptIn, onNewsletterOptIn]);
|
|
260
|
+
|
|
232
261
|
const switchMode = (newMode: AuthMode) => {
|
|
233
262
|
setViewVisible(false);
|
|
234
263
|
setTimeout(() => {
|
|
@@ -388,9 +417,10 @@ export function LoginView({
|
|
|
388
417
|
bootstrapMode={true}
|
|
389
418
|
defaultEmail={defaultEmail}
|
|
390
419
|
defaultPassword={defaultPassword}
|
|
391
|
-
|
|
420
|
+
onSignedIn={subscribeIfOptedIn}
|
|
392
421
|
newsletterOptIn={newsletterOptIn}
|
|
393
422
|
setNewsletterOptIn={setNewsletterOptIn}
|
|
423
|
+
showNewsletterOptIn={Boolean(onNewsletterOptIn)}
|
|
394
424
|
/>
|
|
395
425
|
)}
|
|
396
426
|
|
|
@@ -419,6 +449,42 @@ export function LoginView({
|
|
|
419
449
|
{topComponent}
|
|
420
450
|
</div>
|
|
421
451
|
)}
|
|
452
|
+
{/* Above the buttons, not below them: it is a
|
|
453
|
+
condition on the sign-in you are about to
|
|
454
|
+
make, and it reads as one more line of the
|
|
455
|
+
consent block a host puts in
|
|
456
|
+
`topComponent` — the demo's privacy tick
|
|
457
|
+
sits directly above it.
|
|
458
|
+
|
|
459
|
+
Which is why it pulls back most of the
|
|
460
|
+
column's `gap-3`: at the full gap the two
|
|
461
|
+
ticks sat 24px apart and read as two
|
|
462
|
+
unrelated questions rather than one
|
|
463
|
+
consent block. The offset is against this
|
|
464
|
+
column's own gap, not against whatever
|
|
465
|
+
margins the host's `topComponent` happens
|
|
466
|
+
to carry, so it lands the same way under
|
|
467
|
+
any of them — but only when there is
|
|
468
|
+
something above to hug. With no header
|
|
469
|
+
and no `topComponent` this row is the
|
|
470
|
+
column's first child, and pulling back a
|
|
471
|
+
gap that is not there would crowd it into
|
|
472
|
+
the logo. */}
|
|
473
|
+
{onNewsletterOptIn && (
|
|
474
|
+
<label className={cls(
|
|
475
|
+
"flex items-center gap-2 cursor-pointer",
|
|
476
|
+
(topComponent || title || subtitle) && "-mt-3"
|
|
477
|
+
)}>
|
|
478
|
+
<Checkbox
|
|
479
|
+
checked={newsletterOptIn}
|
|
480
|
+
onCheckedChange={(checked) => setNewsletterOptIn(checked === true)}
|
|
481
|
+
size="small"
|
|
482
|
+
/>
|
|
483
|
+
<Typography variant="caption" color="secondary" className="select-none">
|
|
484
|
+
{t("join_newsletter")}
|
|
485
|
+
</Typography>
|
|
486
|
+
</label>
|
|
487
|
+
)}
|
|
422
488
|
<LoginButton
|
|
423
489
|
disabled={disabled}
|
|
424
490
|
text={"Sign in with email"}
|
|
@@ -430,6 +496,7 @@ export function LoginView({
|
|
|
430
496
|
disabled={disabled}
|
|
431
497
|
googleClientId={googleClientId}
|
|
432
498
|
authController={authController}
|
|
499
|
+
onSignedIn={() => subscribeIfOptedIn(authControllerRef.current.user?.email)}
|
|
433
500
|
/>
|
|
434
501
|
)}
|
|
435
502
|
{hasGitHubLogin && githubClientId && (
|
|
@@ -473,9 +540,7 @@ export function LoginView({
|
|
|
473
540
|
switchToRegister={showRegistration ? () => switchMode("register") : undefined}
|
|
474
541
|
defaultEmail={defaultEmail}
|
|
475
542
|
defaultPassword={defaultPassword}
|
|
476
|
-
|
|
477
|
-
newsletterOptIn={newsletterOptIn}
|
|
478
|
-
setNewsletterOptIn={setNewsletterOptIn}
|
|
543
|
+
onSignedIn={subscribeIfOptedIn}
|
|
479
544
|
/>
|
|
480
545
|
)}
|
|
481
546
|
|
|
@@ -491,9 +556,7 @@ export function LoginView({
|
|
|
491
556
|
switchToLogin={() => switchMode("login")}
|
|
492
557
|
defaultEmail={defaultEmail}
|
|
493
558
|
defaultPassword={defaultPassword}
|
|
494
|
-
|
|
495
|
-
newsletterOptIn={newsletterOptIn}
|
|
496
|
-
setNewsletterOptIn={setNewsletterOptIn}
|
|
559
|
+
onSignedIn={subscribeIfOptedIn}
|
|
497
560
|
/>
|
|
498
561
|
)}
|
|
499
562
|
|
|
@@ -557,13 +620,21 @@ const GoogleIcon = () => (
|
|
|
557
620
|
function GoogleLoginButton({
|
|
558
621
|
disabled,
|
|
559
622
|
googleClientId,
|
|
560
|
-
authController
|
|
623
|
+
authController,
|
|
624
|
+
onSignedIn
|
|
561
625
|
}: {
|
|
562
626
|
disabled?: boolean,
|
|
563
627
|
googleClientId: string,
|
|
564
|
-
authController: AuthControllerExtended
|
|
628
|
+
authController: AuthControllerExtended,
|
|
629
|
+
/** Called once the controller has accepted the provider's code. */
|
|
630
|
+
onSignedIn?: () => void
|
|
565
631
|
}) {
|
|
566
632
|
const codeClientRef = useRef<{ requestCode(): void } | null>(null);
|
|
633
|
+
// The code client is built once and its callback closes over this render's
|
|
634
|
+
// props forever. A ref keeps the callback reaching the *current* handler
|
|
635
|
+
// rather than the one that existed when Google's script happened to load.
|
|
636
|
+
const onSignedInRef = useRef(onSignedIn);
|
|
637
|
+
onSignedInRef.current = onSignedIn;
|
|
567
638
|
|
|
568
639
|
useEffect(() => {
|
|
569
640
|
if (!authController.googleLogin) return;
|
|
@@ -587,6 +658,10 @@ function GoogleLoginButton({
|
|
|
587
658
|
code: response.code,
|
|
588
659
|
redirectUri: "postmessage"
|
|
589
660
|
});
|
|
661
|
+
// After the await, and only on the resolution path: a
|
|
662
|
+
// newsletter tick must not subscribe an address whose
|
|
663
|
+
// sign-in the controller rejected.
|
|
664
|
+
onSignedInRef.current?.();
|
|
590
665
|
} catch (err: unknown) {
|
|
591
666
|
console.error("Google login error:", err);
|
|
592
667
|
}
|
|
@@ -692,9 +767,10 @@ function LoginForm({
|
|
|
692
767
|
switchToLogin,
|
|
693
768
|
defaultEmail,
|
|
694
769
|
defaultPassword,
|
|
695
|
-
|
|
770
|
+
onSignedIn,
|
|
696
771
|
newsletterOptIn = false,
|
|
697
|
-
setNewsletterOptIn
|
|
772
|
+
setNewsletterOptIn,
|
|
773
|
+
showNewsletterOptIn = false
|
|
698
774
|
}: {
|
|
699
775
|
onClose: () => void,
|
|
700
776
|
onForgotPassword?: () => void,
|
|
@@ -707,9 +783,15 @@ function LoginForm({
|
|
|
707
783
|
switchToLogin?: () => void,
|
|
708
784
|
defaultEmail?: string,
|
|
709
785
|
defaultPassword?: string,
|
|
710
|
-
|
|
786
|
+
/** The address the controller just accepted, reported after every success. */
|
|
787
|
+
onSignedIn?: (email: string) => void,
|
|
711
788
|
newsletterOptIn?: boolean,
|
|
712
|
-
setNewsletterOptIn?: (checked: boolean) => void
|
|
789
|
+
setNewsletterOptIn?: (checked: boolean) => void,
|
|
790
|
+
/**
|
|
791
|
+
* Render the newsletter opt-in here. Only bootstrap mode does: everywhere
|
|
792
|
+
* else it lives on the provider screen, which bootstrap mode never shows.
|
|
793
|
+
*/
|
|
794
|
+
showNewsletterOptIn?: boolean
|
|
713
795
|
}) {
|
|
714
796
|
const passwordRef = useRef<HTMLInputElement | null>(null);
|
|
715
797
|
const { t } = useTranslation();
|
|
@@ -749,16 +831,14 @@ function LoginForm({
|
|
|
749
831
|
// Fires only on the resolution path, i.e. after the controller accepted
|
|
750
832
|
// the credentials — a ticked box on a failed attempt must not subscribe
|
|
751
833
|
// an address its owner never proved they control.
|
|
752
|
-
function
|
|
753
|
-
|
|
754
|
-
onNewsletterOptIn(email);
|
|
755
|
-
}
|
|
834
|
+
function reportSignedIn(email: string) {
|
|
835
|
+
onSignedIn?.(email);
|
|
756
836
|
}
|
|
757
837
|
|
|
758
838
|
function handleEnterPassword() {
|
|
759
839
|
if (email && password && authController.emailPasswordLogin) {
|
|
760
840
|
void Promise.resolve(authController.emailPasswordLogin(email, password))
|
|
761
|
-
.then(() =>
|
|
841
|
+
.then(() => reportSignedIn(email))
|
|
762
842
|
.catch(() => undefined);
|
|
763
843
|
}
|
|
764
844
|
}
|
|
@@ -766,7 +846,7 @@ function LoginForm({
|
|
|
766
846
|
function handleRegistration() {
|
|
767
847
|
if (email && password && authController.register) {
|
|
768
848
|
void Promise.resolve(authController.register(email, password, displayName))
|
|
769
|
-
.then(() =>
|
|
849
|
+
.then(() => reportSignedIn(email))
|
|
770
850
|
.catch(() => undefined);
|
|
771
851
|
}
|
|
772
852
|
}
|
|
@@ -891,7 +971,10 @@ function LoginForm({
|
|
|
891
971
|
</div>
|
|
892
972
|
)}
|
|
893
973
|
|
|
894
|
-
{
|
|
974
|
+
{/* Bootstrap mode only — see `showNewsletterOptIn`. Every other
|
|
975
|
+
route to this form came through the provider screen, which is
|
|
976
|
+
where the box now is. */}
|
|
977
|
+
{showNewsletterOptIn && (
|
|
895
978
|
<label className="flex items-center gap-2 cursor-pointer mt-1 mb-1">
|
|
896
979
|
<Checkbox
|
|
897
980
|
checked={newsletterOptIn}
|
|
@@ -24,8 +24,13 @@ export function useTranslation() {
|
|
|
24
24
|
* Also supports i18next interpolation variables, e.g.
|
|
25
25
|
* t("add_to_field", { fieldName: "Tags" })
|
|
26
26
|
* t("error_deleting", { message: err.message })
|
|
27
|
+
* t("sort_key_position", { position: 2 })
|
|
28
|
+
*
|
|
29
|
+
* Numbers are accepted as well as strings: i18next interpolates them
|
|
30
|
+
* either way, and a signature that took only strings pushed a `String()`
|
|
31
|
+
* onto every call site with a count in it — `{{count}}`, `{{position}}`.
|
|
27
32
|
*/
|
|
28
|
-
const typedT = useCallback((key: string, vars?: Record<string, string>): string =>
|
|
33
|
+
const typedT = useCallback((key: string, vars?: Record<string, string | number>): string =>
|
|
29
34
|
t(key, vars) as string, [t]);
|
|
30
35
|
|
|
31
36
|
return useMemo(() => ({ t: typedT,
|
package/src/locales/de.ts
CHANGED
|
@@ -43,6 +43,16 @@ export const de: RebaseTranslations = {
|
|
|
43
43
|
clear_sort: "Sortierung löschen",
|
|
44
44
|
sort: "Sortieren",
|
|
45
45
|
sort_by: "Sortieren nach",
|
|
46
|
+
sort_then_by: "Dann nach",
|
|
47
|
+
sort_ascending: "Aufsteigend sortieren",
|
|
48
|
+
sort_descending: "Absteigend sortieren",
|
|
49
|
+
sort_remove: "Sortierung entfernen",
|
|
50
|
+
sort_move_up: "Diesen Schlüssel wichtiger machen",
|
|
51
|
+
sort_move_down: "Diesen Schlüssel weniger wichtig machen",
|
|
52
|
+
sort_remove_key: "Diesen Sortierschlüssel entfernen",
|
|
53
|
+
sort_key_position: "Sortierschlüssel {{position}}",
|
|
54
|
+
sort_shift_click_hint: "Umschalt+Klick, um eine Spalte unter der aktuellen Sortierung hinzuzufügen",
|
|
55
|
+
save_entity_before_subcollections: "Du musst den Eintrag speichern, bevor du weitere Sammlungen hinzufügst",
|
|
46
56
|
clear_all: "Alle löschen",
|
|
47
57
|
no_items: "Keine Elemente",
|
|
48
58
|
no_entries_found: "Keine Einträge gefunden",
|
package/src/locales/en.ts
CHANGED
|
@@ -53,6 +53,16 @@ export const en: RebaseTranslations = {
|
|
|
53
53
|
clear_sort: "Clear sort",
|
|
54
54
|
sort: "Sort",
|
|
55
55
|
sort_by: "Sort by",
|
|
56
|
+
sort_then_by: "Then by",
|
|
57
|
+
sort_ascending: "Sort ascending",
|
|
58
|
+
sort_descending: "Sort descending",
|
|
59
|
+
sort_remove: "Remove sort",
|
|
60
|
+
sort_move_up: "Make this key more important",
|
|
61
|
+
sort_move_down: "Make this key less important",
|
|
62
|
+
sort_remove_key: "Remove this sort key",
|
|
63
|
+
sort_key_position: "Sort key {{position}}",
|
|
64
|
+
sort_shift_click_hint: "Shift-click to add a column under the current sort",
|
|
65
|
+
save_entity_before_subcollections: "You need to save your entity before adding additional collections",
|
|
56
66
|
clear_all: "Clear all",
|
|
57
67
|
no_items: "No items",
|
|
58
68
|
no_entries_found: "No entries found",
|
package/src/locales/es.ts
CHANGED
|
@@ -51,6 +51,16 @@ export const es: RebaseTranslations = {
|
|
|
51
51
|
clear_sort: "Borrar orden",
|
|
52
52
|
sort: "Ordenar",
|
|
53
53
|
sort_by: "Ordenar por",
|
|
54
|
+
sort_then_by: "Luego por",
|
|
55
|
+
sort_ascending: "Ordenar ascendente",
|
|
56
|
+
sort_descending: "Ordenar descendente",
|
|
57
|
+
sort_remove: "Quitar orden",
|
|
58
|
+
sort_move_up: "Dar más importancia a esta clave",
|
|
59
|
+
sort_move_down: "Dar menos importancia a esta clave",
|
|
60
|
+
sort_remove_key: "Quitar esta clave de orden",
|
|
61
|
+
sort_key_position: "Clave de orden {{position}}",
|
|
62
|
+
sort_shift_click_hint: "Mayús+clic para añadir una columna bajo el orden actual",
|
|
63
|
+
save_entity_before_subcollections: "Debes guardar la entidad antes de añadir colecciones adicionales",
|
|
54
64
|
clear_all: "Borrar todo",
|
|
55
65
|
no_items: "Sin elementos",
|
|
56
66
|
no_entries_found: "No se encontraron entradas",
|
package/src/locales/fr.ts
CHANGED
|
@@ -43,6 +43,16 @@ export const fr: RebaseTranslations = {
|
|
|
43
43
|
clear_sort: "Effacer le tri",
|
|
44
44
|
sort: "Trier",
|
|
45
45
|
sort_by: "Trier par",
|
|
46
|
+
sort_then_by: "Puis par",
|
|
47
|
+
sort_ascending: "Trier par ordre croissant",
|
|
48
|
+
sort_descending: "Trier par ordre décroissant",
|
|
49
|
+
sort_remove: "Supprimer le tri",
|
|
50
|
+
sort_move_up: "Rendre cette clé plus importante",
|
|
51
|
+
sort_move_down: "Rendre cette clé moins importante",
|
|
52
|
+
sort_remove_key: "Supprimer cette clé de tri",
|
|
53
|
+
sort_key_position: "Clé de tri {{position}}",
|
|
54
|
+
sort_shift_click_hint: "Maj+clic pour ajouter une colonne sous le tri actuel",
|
|
55
|
+
save_entity_before_subcollections: "Vous devez enregistrer l'entité avant d'ajouter des collections supplémentaires",
|
|
46
56
|
clear_all: "Tout effacer",
|
|
47
57
|
no_items: "Aucun élément",
|
|
48
58
|
no_entries_found: "Aucune entrée trouvée",
|
package/src/locales/hi.ts
CHANGED
|
@@ -43,6 +43,16 @@ export const hi: RebaseTranslations = {
|
|
|
43
43
|
clear_sort: "सॉर्ट साफ़ करें",
|
|
44
44
|
sort: "क्रमबद्ध करें",
|
|
45
45
|
sort_by: "इसके अनुसार क्रमबद्ध करें",
|
|
46
|
+
sort_then_by: "फिर इसके अनुसार",
|
|
47
|
+
sort_ascending: "आरोही क्रम में लगाएँ",
|
|
48
|
+
sort_descending: "अवरोही क्रम में लगाएँ",
|
|
49
|
+
sort_remove: "क्रम हटाएँ",
|
|
50
|
+
sort_move_up: "इस कुंजी को अधिक महत्व दें",
|
|
51
|
+
sort_move_down: "इस कुंजी को कम महत्व दें",
|
|
52
|
+
sort_remove_key: "यह क्रम कुंजी हटाएँ",
|
|
53
|
+
sort_key_position: "क्रम कुंजी {{position}}",
|
|
54
|
+
sort_shift_click_hint: "वर्तमान क्रम के नीचे कॉलम जोड़ने के लिए Shift+क्लिक करें",
|
|
55
|
+
save_entity_before_subcollections: "अतिरिक्त संग्रह जोड़ने से पहले आपको एंटिटी सहेजनी होगी",
|
|
46
56
|
clear_all: "सभी साफ़ करें",
|
|
47
57
|
no_items: "कोई आइटम नहीं",
|
|
48
58
|
no_entries_found: "कोई प्रविष्टि नहीं मिली",
|
package/src/locales/it.ts
CHANGED
|
@@ -43,6 +43,16 @@ export const it: RebaseTranslations = {
|
|
|
43
43
|
clear_sort: "Cancella ordinamento",
|
|
44
44
|
sort: "Ordina",
|
|
45
45
|
sort_by: "Ordina per",
|
|
46
|
+
sort_then_by: "Poi per",
|
|
47
|
+
sort_ascending: "Ordina in modo crescente",
|
|
48
|
+
sort_descending: "Ordina in modo decrescente",
|
|
49
|
+
sort_remove: "Rimuovi ordinamento",
|
|
50
|
+
sort_move_up: "Rendi questa chiave più importante",
|
|
51
|
+
sort_move_down: "Rendi questa chiave meno importante",
|
|
52
|
+
sort_remove_key: "Rimuovi questa chiave di ordinamento",
|
|
53
|
+
sort_key_position: "Chiave di ordinamento {{position}}",
|
|
54
|
+
sort_shift_click_hint: "Maiusc+clic per aggiungere una colonna sotto l'ordinamento attuale",
|
|
55
|
+
save_entity_before_subcollections: "Devi salvare l'entità prima di aggiungere altre raccolte",
|
|
46
56
|
clear_all: "Cancella tutto",
|
|
47
57
|
no_items: "Nessun elemento",
|
|
48
58
|
no_entries_found: "Nessuna voce trovata",
|
package/src/locales/pt.ts
CHANGED
|
@@ -48,6 +48,16 @@ export const pt: RebaseTranslations = {
|
|
|
48
48
|
clear_sort: "Limpar ordenação",
|
|
49
49
|
sort: "Ordenar",
|
|
50
50
|
sort_by: "Ordenar por",
|
|
51
|
+
sort_then_by: "Depois por",
|
|
52
|
+
sort_ascending: "Ordenar ascendente",
|
|
53
|
+
sort_descending: "Ordenar descendente",
|
|
54
|
+
sort_remove: "Remover ordenação",
|
|
55
|
+
sort_move_up: "Tornar esta chave mais importante",
|
|
56
|
+
sort_move_down: "Tornar esta chave menos importante",
|
|
57
|
+
sort_remove_key: "Remover esta chave de ordenação",
|
|
58
|
+
sort_key_position: "Chave de ordenação {{position}}",
|
|
59
|
+
sort_shift_click_hint: "Shift+clique para adicionar uma coluna sob a ordenação atual",
|
|
60
|
+
save_entity_before_subcollections: "Precisas de guardar a entidade antes de adicionar coleções adicionais",
|
|
51
61
|
clear_all: "Limpar tudo",
|
|
52
62
|
no_items: "Sem itens",
|
|
53
63
|
no_entries_found: "Nenhum registo encontrado",
|