@greatapps/common 1.1.666 → 1.1.668

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.
Files changed (103) hide show
  1. package/dist/components/account/ConfigurationsMyAccountModal.mjs +8 -6
  2. package/dist/components/account/ConfigurationsMyAccountModal.mjs.map +1 -1
  3. package/dist/components/account/ConfirmDeleteAccountModal.mjs +20 -19
  4. package/dist/components/account/ConfirmDeleteAccountModal.mjs.map +1 -1
  5. package/dist/components/account/ConfirmGlobalPreferencesModal.mjs +10 -11
  6. package/dist/components/account/ConfirmGlobalPreferencesModal.mjs.map +1 -1
  7. package/dist/components/account/DeleteAccountModal.mjs +34 -28
  8. package/dist/components/account/DeleteAccountModal.mjs.map +1 -1
  9. package/dist/components/account/DisableTwoFactorAuthModal.mjs +7 -5
  10. package/dist/components/account/DisableTwoFactorAuthModal.mjs.map +1 -1
  11. package/dist/components/account/IsntPossibleDeleteAccountModal.mjs +8 -9
  12. package/dist/components/account/IsntPossibleDeleteAccountModal.mjs.map +1 -1
  13. package/dist/components/account/TwoFactorAuthModal.mjs +22 -23
  14. package/dist/components/account/TwoFactorAuthModal.mjs.map +1 -1
  15. package/dist/components/account/hooks/useChangeEmailForm.mjs +5 -3
  16. package/dist/components/account/hooks/useChangeEmailForm.mjs.map +1 -1
  17. package/dist/components/account/hooks/useChangePasswordForm.mjs +26 -3
  18. package/dist/components/account/hooks/useChangePasswordForm.mjs.map +1 -1
  19. package/dist/components/account/hooks/useChangePhoneForm.mjs +18 -9
  20. package/dist/components/account/hooks/useChangePhoneForm.mjs.map +1 -1
  21. package/dist/components/account/hooks/useDisableTwoFactorAuthForm.mjs +26 -3
  22. package/dist/components/account/hooks/useDisableTwoFactorAuthForm.mjs.map +1 -1
  23. package/dist/components/account/hooks/useOtpVerification.mjs +19 -3
  24. package/dist/components/account/hooks/useOtpVerification.mjs.map +1 -1
  25. package/dist/components/account/hooks/useTwoFactorAuthForm.mjs +27 -4
  26. package/dist/components/account/hooks/useTwoFactorAuthForm.mjs.map +1 -1
  27. package/dist/components/account/sections/ChangeEmailModal.mjs +25 -28
  28. package/dist/components/account/sections/ChangeEmailModal.mjs.map +1 -1
  29. package/dist/components/account/sections/ChangePasswordSection.mjs +8 -6
  30. package/dist/components/account/sections/ChangePasswordSection.mjs.map +1 -1
  31. package/dist/components/account/sections/ChangePhoneModal.mjs +24 -27
  32. package/dist/components/account/sections/ChangePhoneModal.mjs.map +1 -1
  33. package/dist/components/account/sections/MyProfileSection.mjs +37 -22
  34. package/dist/components/account/sections/MyProfileSection.mjs.map +1 -1
  35. package/dist/components/account/sections/PreferencesSection.mjs +48 -24
  36. package/dist/components/account/sections/PreferencesSection.mjs.map +1 -1
  37. package/dist/components/account/sections/SecuritySection.mjs +11 -9
  38. package/dist/components/account/sections/SecuritySection.mjs.map +1 -1
  39. package/dist/components/account/sections/TokenSection.mjs +24 -8
  40. package/dist/components/account/sections/TokenSection.mjs.map +1 -1
  41. package/dist/i18n/config.mjs +13 -0
  42. package/dist/i18n/config.mjs.map +1 -0
  43. package/dist/i18n/country-language.mjs +74 -0
  44. package/dist/i18n/country-language.mjs.map +1 -0
  45. package/dist/i18n/messages/en-us.mjs +219 -0
  46. package/dist/i18n/messages/en-us.mjs.map +1 -0
  47. package/dist/i18n/messages/es-es.mjs +219 -0
  48. package/dist/i18n/messages/es-es.mjs.map +1 -0
  49. package/dist/i18n/messages/index.mjs +16 -0
  50. package/dist/i18n/messages/index.mjs.map +1 -0
  51. package/dist/i18n/messages/pt-br.mjs +224 -0
  52. package/dist/i18n/messages/pt-br.mjs.map +1 -0
  53. package/dist/i18n/normalize.mjs +12 -0
  54. package/dist/i18n/normalize.mjs.map +1 -0
  55. package/dist/i18n/resolve-locale.mjs +42 -0
  56. package/dist/i18n/resolve-locale.mjs.map +1 -0
  57. package/dist/i18n.mjs +14 -0
  58. package/dist/i18n.mjs.map +1 -0
  59. package/dist/providers/auth.provider.mjs +9 -1
  60. package/dist/providers/auth.provider.mjs.map +1 -1
  61. package/dist/server.mjs +2 -0
  62. package/dist/server.mjs.map +1 -1
  63. package/dist/utils/intl/cookie-domain.mjs +17 -0
  64. package/dist/utils/intl/cookie-domain.mjs.map +1 -0
  65. package/dist/utils/intl/locale-cookie.mjs +13 -0
  66. package/dist/utils/intl/locale-cookie.mjs.map +1 -0
  67. package/dist/utils/intl/locales.mjs +3 -2
  68. package/dist/utils/intl/locales.mjs.map +1 -1
  69. package/package.json +7 -1
  70. package/src/components/account/ConfigurationsMyAccountModal.tsx +10 -6
  71. package/src/components/account/ConfirmDeleteAccountModal.tsx +22 -13
  72. package/src/components/account/ConfirmGlobalPreferencesModal.tsx +12 -10
  73. package/src/components/account/DeleteAccountModal.tsx +34 -24
  74. package/src/components/account/DisableTwoFactorAuthModal.tsx +11 -6
  75. package/src/components/account/IsntPossibleDeleteAccountModal.tsx +8 -6
  76. package/src/components/account/TwoFactorAuthModal.tsx +33 -29
  77. package/src/components/account/hooks/useChangeEmailForm.tsx +5 -3
  78. package/src/components/account/hooks/useChangePasswordForm.tsx +20 -6
  79. package/src/components/account/hooks/useChangePhoneForm.tsx +16 -10
  80. package/src/components/account/hooks/useDisableTwoFactorAuthForm.tsx +20 -6
  81. package/src/components/account/hooks/useOtpVerification.tsx +16 -4
  82. package/src/components/account/hooks/useTwoFactorAuthForm.tsx +21 -7
  83. package/src/components/account/sections/ChangeEmailModal.tsx +35 -21
  84. package/src/components/account/sections/ChangePasswordSection.tsx +10 -6
  85. package/src/components/account/sections/ChangePhoneModal.tsx +50 -22
  86. package/src/components/account/sections/MyProfileSection.tsx +37 -23
  87. package/src/components/account/sections/PreferencesSection.tsx +52 -25
  88. package/src/components/account/sections/SecuritySection.tsx +19 -10
  89. package/src/components/account/sections/TokenSection.tsx +22 -10
  90. package/src/i18n/config.ts +17 -0
  91. package/src/i18n/country-language.ts +60 -0
  92. package/src/i18n/messages/en-us.ts +234 -0
  93. package/src/i18n/messages/es-es.ts +235 -0
  94. package/src/i18n/messages/index.ts +24 -0
  95. package/src/i18n/messages/pt-br.ts +244 -0
  96. package/src/i18n/normalize.ts +15 -0
  97. package/src/i18n/resolve-locale.ts +77 -0
  98. package/src/i18n.ts +10 -0
  99. package/src/providers/auth.provider.tsx +13 -1
  100. package/src/server.ts +2 -0
  101. package/src/utils/intl/cookie-domain.ts +29 -0
  102. package/src/utils/intl/locale-cookie.ts +18 -0
  103. package/src/utils/intl/locales.ts +3 -2
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Catálogo de traducción es-es para los componentes de @greatapps/common.
3
+ * Las apps consumidoras combinan este objeto bajo el namespace `common`.
4
+ * Refleja la estructura de módulos de `pt-br.ts` (la fuente de verdad).
5
+ */
6
+ const messages = {
7
+ actions: {
8
+ save: 'Guardar',
9
+ saveChanges: 'Guardar cambios',
10
+ cancel: 'Cancelar',
11
+ confirm: 'Confirmar',
12
+ delete: 'Eliminar',
13
+ edit: 'Editar',
14
+ close: 'Cerrar',
15
+ back: 'Volver',
16
+ continue: 'Continuar',
17
+ add: 'Agregar',
18
+ remove: 'Quitar',
19
+ search: 'Buscar',
20
+ loading: 'Cargando...',
21
+ saving: 'Guardando...',
22
+ sending: 'Enviando...',
23
+ copy: 'Copiar',
24
+ copied: 'Copiado',
25
+ },
26
+
27
+ validations: {
28
+ required: 'Campo obligatorio',
29
+ invalidEmail: 'Correo electrónico inválido',
30
+ invalidPhone: 'Teléfono inválido',
31
+ },
32
+
33
+ preferences: {
34
+ title: 'Preferencias',
35
+ companyName: 'Nombre de la empresa',
36
+ companyNamePlaceholder: 'Ingrese el nombre de la empresa',
37
+ language: 'Idioma',
38
+ languagePlaceholder: 'Seleccione el idioma',
39
+ languageSearch: 'Buscar idioma...',
40
+ currency: 'Moneda',
41
+ currencyPlaceholder: 'Seleccione la moneda',
42
+ currencySearch: 'Buscar moneda...',
43
+ timeDisplay: 'Visualización de hora',
44
+ timezone: 'Zona horaria',
45
+ timezonePlaceholder: 'Seleccione la zona horaria',
46
+ timezoneSearch: 'Buscar zona horaria...',
47
+ timeFormat: 'Formato de hora',
48
+ timeFormatPlaceholder: 'Seleccione el formato',
49
+ timeFormatOptions: {
50
+ h24: '24 horas',
51
+ h12: '12 horas (AM/PM)',
52
+ },
53
+ savedSuccess: 'Preferencias guardadas con éxito',
54
+ saveError: 'Error al guardar las preferencias',
55
+ },
56
+
57
+ account: {
58
+ deleteAccount: {
59
+ title: 'Eliminar cuenta',
60
+ description:
61
+ 'Esta acción es <b>irreversible</b>. Al confirmar, su cuenta y todos sus datos serán eliminados.',
62
+ reasonLabel: 'Motivo de la cancelación',
63
+ reasonPlaceholder: 'Seleccionar',
64
+ descriptionLabel: 'Describa qué lo llevó a tomar esta decisión',
65
+ descriptionPlaceholder: 'Explique su decisión...',
66
+ charactersCount: '{count}/400 caracteres',
67
+ passwordLabel: 'Contraseña',
68
+ passwordPlaceholder: 'Ingrese su contraseña',
69
+ submit: 'Eliminar cuenta',
70
+ requiredFields: 'Complete todos los campos obligatorios',
71
+ reasons: {
72
+ noFeature: 'La herramienta no tiene una función que necesito',
73
+ technical: 'Encontré problemas técnicos o errores en el sistema',
74
+ support: 'Tuve problemas con el soporte o atención al cliente',
75
+ updates: 'Las actualizaciones tardan más de lo esperado',
76
+ temporary: 'La cancelación es temporal',
77
+ other: 'Otro motivo',
78
+ },
79
+ },
80
+ confirmDelete: {
81
+ title: '¡Qué pena verte partir!',
82
+ warning:
83
+ 'Todas sus <b>{count} páginas</b> serán eliminadas permanentemente. ¡Esta acción es <b>irreversible!</b>',
84
+ sessionExpired: 'Sesión expirada. Complete los campos nuevamente.',
85
+ success: 'Cuenta eliminada exitosamente',
86
+ error: 'Error al eliminar la cuenta. Intente nuevamente.',
87
+ keepAccount: 'Mantener mi cuenta',
88
+ deleteAnyway: 'Eliminar de todas formas',
89
+ deleting: 'Eliminando...',
90
+ },
91
+ cantDelete: {
92
+ title: 'No es posible eliminar la cuenta',
93
+ description:
94
+ 'Tiene una suscripción activa. Para eliminar su cuenta, <b>primero cancele su suscripción</b> en la página Suscripción e intente nuevamente.',
95
+ confirm: 'Ok, entendido',
96
+ goToSubscription: 'Ir a suscripción',
97
+ },
98
+ twoFactor: {
99
+ title: 'Autenticación de dos factores',
100
+ instructions:
101
+ 'Instale una aplicación autenticadora en su dispositivo móvil (p. ej., <link>Google Authenticator</link>), escanee el código QR al lado o copie la clave en la aplicación y, a continuación, ingrese el código de 6 dígitos generado para continuar.',
102
+ qrAlt: 'Código QR para autenticación de dos factores',
103
+ retry: 'Intentar de nuevo',
104
+ pasteDigits: 'Pegue los 6 dígitos abajo',
105
+ activating: 'Activando...',
106
+ activate: 'Activar autenticación',
107
+ recoveryTitle: 'Códigos de seguridad',
108
+ recoveryHeading: 'Guarde estos códigos de recuperación de emergencia',
109
+ recoveryText1:
110
+ 'Si pierde acceso a su teléfono, no podrá iniciar sesión en su cuenta sin un código de dos factores.',
111
+ recoveryText2: 'Imprima, copie o escriba los códigos a continuación en un lugar seguro.',
112
+ copyCodes: 'Copiar códigos',
113
+ finish: 'Finalizar',
114
+ codesCopied: 'Códigos copiados correctamente',
115
+ generateError: 'Error al generar el código QR. Intente de nuevo.',
116
+ confirmError: 'Error al confirmar el código. Intente de nuevo.',
117
+ disableTitle: 'Desactivar autenticación 2FA',
118
+ disableDescription:
119
+ 'Ingrese el código de 6 dígitos de su aplicación autenticadora para confirmar la desactivación.',
120
+ authenticatorCode: 'Código del autenticador',
121
+ disabling: 'Desactivando...',
122
+ disable: 'Desactivar 2FA',
123
+ disabledSuccess: 'Autenticación de dos factores desactivada',
124
+ disableError: 'Error al desactivar 2FA. Intente de nuevo.',
125
+ invalidCode: 'Código inválido',
126
+ codeResent: 'Código reenviado',
127
+ resendError: 'Error al reenviar el código. Intente de nuevo.',
128
+ },
129
+ profile: {
130
+ title: 'Mi perfil',
131
+ avatarAlt: 'Foto del usuario',
132
+ changePhoto: 'Cambiar foto',
133
+ name: 'Nombre',
134
+ namePlaceholder: 'Ingrese su nombre',
135
+ lastName: 'Apellido',
136
+ lastNamePlaceholder: 'Ingrese su apellido',
137
+ gender: 'Género',
138
+ genderPlaceholder: 'Seleccione su género',
139
+ genderOptions: {
140
+ male: 'Masculino',
141
+ female: 'Femenino',
142
+ },
143
+ phone: 'Teléfono',
144
+ change: 'Cambiar',
145
+ accessData: 'Datos de acceso',
146
+ email: 'Correo electrónico',
147
+ savedSuccess: 'Perfil actualizado exitosamente',
148
+ saveError: 'Error al actualizar el perfil',
149
+ },
150
+ otp: {
151
+ validationCode: 'Código de validación',
152
+ incorrectCode: 'Código incorrecto',
153
+ resend: 'Reenviar código',
154
+ resendIn: 'Reenviar en <b>{time}</b>',
155
+ validating: 'Validando...',
156
+ },
157
+ changeEmail: {
158
+ title: 'Cambiar correo electrónico',
159
+ confirm: 'Cambiar correo electrónico',
160
+ sentCodeTo: 'Enviamos un código de validación al correo electrónico <b>{email}.</b>',
161
+ updateTitle: 'Actualizar correo electrónico de contacto',
162
+ updateDescription:
163
+ 'Indique su nuevo correo electrónico para mantener su información de contacto actualizada.',
164
+ newEmail: 'Nuevo correo electrónico',
165
+ newEmailPlaceholder: 'Ingrese su nuevo correo electrónico',
166
+ emailChanged: 'Correo electrónico cambiado exitosamente',
167
+ emailExistsError: 'El correo electrónico ya existe o ocurrió un error. Intente más tarde.',
168
+ },
169
+ changePhone: {
170
+ titleEdit: 'Cambiar teléfono',
171
+ titleAdd: 'Agregar teléfono',
172
+ sentCodeTo: 'Enviamos un código de validación al número actual <b>{phone}.</b>',
173
+ confirm: 'Cambiar teléfono',
174
+ updateTitle: 'Actualizar teléfono de contacto',
175
+ addTitle: 'Agregar teléfono de contacto',
176
+ updateDescription: 'Indique el nuevo número para mantener su información de contacto actualizada.',
177
+ addDescription: 'Indique su número de teléfono para contacto.',
178
+ newNumber: 'Nuevo número',
179
+ numberLabel: 'Número de teléfono',
180
+ phoneChanged: 'Teléfono cambiado',
181
+ phoneAdded: 'Teléfono agregado',
182
+ invalidNumber: 'Número de teléfono inválido',
183
+ saveError: 'Ocurrió un error al guardar el número de teléfono. Intente más tarde.',
184
+ numberExistsError: 'El número ya existe o ocurrió un error. Intente más tarde.',
185
+ },
186
+ security: {
187
+ title: 'Seguridad',
188
+ googleLinkedTitle: 'Cuenta Google vinculada',
189
+ googleLinkedDescription:
190
+ 'Su cuenta está vinculada a Google. Si la desvincula, utilice "Olvidé mi contraseña" para restablecer una contraseña de acceso por correo electrónico.',
191
+ unlinking: 'Desvinculando...',
192
+ unlinkGoogle: 'Desvincular Google',
193
+ unlinkError: 'Error al desvincular la cuenta Google.',
194
+ deleteAccountDescription:
195
+ 'Esta acción es permanente y no se puede deshacer. Todos sus datos, proyectos y configuraciones serán eliminados permanentemente.',
196
+ deleteMyAccount: 'Eliminar mi cuenta',
197
+ },
198
+ token: {
199
+ title: 'Token de la cuenta',
200
+ label: 'Token',
201
+ copySuccess: 'Token copiado correctamente',
202
+ copyError: 'No se pudo copiar el token',
203
+ info: 'Este token es exclusivo de su cuenta y permite el acceso a la API. No lo comparta con terceros.',
204
+ docsLink: 'Acceder a la documentación',
205
+ },
206
+ changePassword: {
207
+ title: 'Cambiar contraseña',
208
+ currentPassword: 'Contraseña actual',
209
+ currentPasswordPlaceholder: 'Ingrese su contraseña actual',
210
+ newPassword: 'Nueva contraseña',
211
+ newPasswordPlaceholder: 'Ingrese su nueva contraseña',
212
+ submit: 'Guardar nueva contraseña',
213
+ changeError: 'Error al cambiar la contraseña',
214
+ changedSuccess: 'Contraseña cambiada correctamente',
215
+ genericError: 'Error al cambiar la contraseña. Intente nuevamente.',
216
+ },
217
+ globalPreferences: {
218
+ title: 'Cambiar preferencias globales',
219
+ srDescription: 'Confirmación de cambio de preferencias globales de la cuenta',
220
+ warning:
221
+ 'Está cambiando configuraciones que impactan <b>todos los usuarios de la cuenta</b>. ¿Desea confirmar?',
222
+ },
223
+ configurations: {
224
+ title: 'Configuración',
225
+ },
226
+ },
227
+
228
+ languages: {
229
+ 'pt-br': 'Portugués (Brasil)',
230
+ 'en-us': 'Inglés (EE. UU.)',
231
+ 'es-es': 'Español',
232
+ },
233
+ } as const;
234
+
235
+ export default messages;
@@ -0,0 +1,24 @@
1
+ import { DEFAULT_LOCALE, Locale } from '../config';
2
+ import enUs from './en-us';
3
+ import esEs from './es-es';
4
+ import ptBr from './pt-br';
5
+
6
+ const COMMON_MESSAGES: Record<Locale, Record<string, unknown>> = {
7
+ 'pt-br': ptBr,
8
+ 'en-us': enUs,
9
+ 'es-es': esEs,
10
+ };
11
+
12
+ /**
13
+ * Forma das mensagens globais (derivada do pt-br, a fonte da verdade).
14
+ * Os apps usam este tipo para a type augmentation do next-intl (chaves `common.*` tipadas).
15
+ */
16
+ export type CommonMessages = typeof ptBr;
17
+
18
+ /**
19
+ * Mensagens dos componentes da @greatapps/common para o locale informado.
20
+ * Os apps mesclam o resultado sob o namespace `common` ao montar o catálogo do next-intl.
21
+ */
22
+ export function getCommonMessages(locale: Locale): Record<string, unknown> {
23
+ return COMMON_MESSAGES[locale] ?? COMMON_MESSAGES[DEFAULT_LOCALE];
24
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Catálogo de traduções pt-br dos componentes da @greatapps/common.
3
+ * Os apps consumidores mesclam este objeto sob o namespace `common`.
4
+ *
5
+ * Padrão modular: cada chave de primeiro nível é um **módulo** (um agrupamento por
6
+ * componente/domínio). Para adicionar traduções, crie/edite o módulo correspondente aqui
7
+ * e replique a mesma estrutura em `en-us.ts` e `es-es.ts`.
8
+ *
9
+ * pt-br é a fonte da verdade — os textos saem verbatim do que o componente já exibe hoje.
10
+ */
11
+ const messages = {
12
+ // Vocabulário global de ações (botões, comandos).
13
+ actions: {
14
+ save: 'Salvar',
15
+ saveChanges: 'Salvar alterações',
16
+ cancel: 'Cancelar',
17
+ confirm: 'Confirmar',
18
+ delete: 'Excluir',
19
+ edit: 'Editar',
20
+ close: 'Fechar',
21
+ back: 'Voltar',
22
+ continue: 'Continuar',
23
+ add: 'Adicionar',
24
+ remove: 'Remover',
25
+ search: 'Buscar',
26
+ loading: 'Carregando...',
27
+ saving: 'Salvando...',
28
+ sending: 'Enviando...',
29
+ copy: 'Copiar',
30
+ copied: 'Copiado',
31
+ },
32
+
33
+ // Mensagens de validação de formulário.
34
+ validations: {
35
+ required: 'Campo obrigatório',
36
+ invalidEmail: 'E-mail inválido',
37
+ invalidPhone: 'Telefone inválido',
38
+ },
39
+
40
+ // Componente: PreferencesSection.
41
+ preferences: {
42
+ title: 'Preferências',
43
+ companyName: 'Nome da empresa',
44
+ companyNamePlaceholder: 'Digite o nome da empresa',
45
+ language: 'Idioma',
46
+ languagePlaceholder: 'Selecione o idioma',
47
+ languageSearch: 'Buscar idioma...',
48
+ currency: 'Moeda',
49
+ currencyPlaceholder: 'Selecione a moeda',
50
+ currencySearch: 'Buscar moeda...',
51
+ timeDisplay: 'Exibição hora',
52
+ timezone: 'Fuso horário',
53
+ timezonePlaceholder: 'Selecione o fuso horário',
54
+ timezoneSearch: 'Buscar fuso horário...',
55
+ timeFormat: 'Formato hora',
56
+ timeFormatPlaceholder: 'Selecione o formato',
57
+ timeFormatOptions: {
58
+ h24: '24 horas',
59
+ h12: '12 horas (AM/PM)',
60
+ },
61
+ savedSuccess: 'Preferências salvas com sucesso',
62
+ saveError: 'Erro ao salvar preferências',
63
+ },
64
+
65
+ // Conta — modais e seções. `<b>` = trecho em negrito (rich text); `{count}` = interpolação.
66
+ account: {
67
+ deleteAccount: {
68
+ title: 'Excluir conta',
69
+ description:
70
+ 'Esta ação é <b>irreversível</b>. Ao confirmar, sua conta e todos os seus dados serão excluídos.',
71
+ reasonLabel: 'Motivo do cancelamento',
72
+ reasonPlaceholder: 'Selecione',
73
+ descriptionLabel: 'Descreva o que levou a tomar essa decisão',
74
+ descriptionPlaceholder: 'Explique sua decisão...',
75
+ charactersCount: '{count}/400 caracteres',
76
+ passwordLabel: 'Senha',
77
+ passwordPlaceholder: 'Informe sua senha',
78
+ submit: 'Excluir conta',
79
+ requiredFields: 'Preencha todos os campos obrigatórios',
80
+ reasons: {
81
+ noFeature: 'A ferramenta não possui uma função que eu preciso',
82
+ technical: 'Encontrei problemas técnicos ou erros no sistema',
83
+ support: 'Tive problemas com o suporte ou atendimento',
84
+ updates: 'As atualizações demoram mais do que o esperado',
85
+ temporary: 'O cancelamento é temporário',
86
+ other: 'Outro motivo',
87
+ },
88
+ },
89
+ confirmDelete: {
90
+ title: 'Uma pena te ver ir!',
91
+ warning:
92
+ 'Todas suas <b>{count} páginas</b> serão deletadas permanentemente. Essa ação é <b>irreversível!</b>',
93
+ sessionExpired: 'Sessão expirada. Preencha os campos novamente.',
94
+ success: 'Conta excluída com sucesso',
95
+ error: 'Erro ao excluir. Tente novamente.',
96
+ keepAccount: 'Manter minha conta',
97
+ deleteAnyway: 'Excluir mesmo assim',
98
+ deleting: 'Excluindo...',
99
+ },
100
+ cantDelete: {
101
+ title: 'Não é possível excluir a conta',
102
+ description:
103
+ 'Você tem uma assinatura ativa. Para excluir sua conta, <b>primeiro cancele a assinatura</b> na página Assinatura e depois tente novamente.',
104
+ confirm: 'Ok, entendi',
105
+ goToSubscription: 'Ir para assinatura',
106
+ },
107
+ twoFactor: {
108
+ title: 'Autenticação de dois fatores',
109
+ instructions:
110
+ 'Instale um aplicativo autenticador no seu celular (ex.: <link>Google Authenticator</link>), escaneie o QR Code ao lado ou copie a chave no aplicativo e, em seguida, digite o código de 6 dígitos gerado para continuar.',
111
+ qrAlt: 'QR Code para autenticação de dois fatores',
112
+ retry: 'Tentar novamente',
113
+ pasteDigits: 'Cole os 6 dígitos abaixo',
114
+ activating: 'Ativando...',
115
+ activate: 'Ativar autenticação',
116
+ recoveryTitle: 'Códigos de segurança',
117
+ recoveryHeading: 'Salve estes códigos de recuperação de emergência',
118
+ recoveryText1:
119
+ 'Se você perder acesso ao seu telefone, não será possível fazer login em sua conta sem um código de dois fatores.',
120
+ recoveryText2: 'Imprima, copie ou escreva em um local seguro os códigos abaixo.',
121
+ copyCodes: 'Copiar códigos',
122
+ finish: 'Finalizar',
123
+ codesCopied: 'Códigos copiados com sucesso',
124
+ generateError: 'Erro ao gerar QR Code. Tente novamente.',
125
+ confirmError: 'Erro ao confirmar código. Tente novamente.',
126
+ disableTitle: 'Desabilitar autenticação 2FA',
127
+ disableDescription:
128
+ 'Informe o código de 6 dígitos do seu aplicativo autenticador para confirmar a desativação.',
129
+ authenticatorCode: 'Código do autenticador',
130
+ disabling: 'Desabilitando...',
131
+ disable: 'Desabilitar 2FA',
132
+ disabledSuccess: 'Autenticação de dois fatores desabilitada',
133
+ disableError: 'Erro ao desabilitar 2FA. Tente novamente.',
134
+ invalidCode: 'Código inválido',
135
+ codeResent: 'Código reenviado',
136
+ resendError: 'Erro ao reenviar código. Tente novamente.',
137
+ },
138
+ profile: {
139
+ title: 'Meu perfil',
140
+ avatarAlt: 'Foto do usuário',
141
+ changePhoto: 'Alterar foto',
142
+ name: 'Nome',
143
+ namePlaceholder: 'Digite seu nome',
144
+ lastName: 'Sobrenome',
145
+ lastNamePlaceholder: 'Digite seu sobrenome',
146
+ gender: 'Gênero',
147
+ genderPlaceholder: 'Selecione seu gênero',
148
+ genderOptions: {
149
+ male: 'Masculino',
150
+ female: 'Feminino',
151
+ },
152
+ phone: 'Telefone',
153
+ change: 'Alterar',
154
+ accessData: 'Dados de acesso',
155
+ email: 'E-mail',
156
+ savedSuccess: 'Perfil atualizado com sucesso',
157
+ saveError: 'Erro ao atualizar perfil',
158
+ },
159
+ otp: {
160
+ validationCode: 'Código de validação',
161
+ incorrectCode: 'Código incorreto',
162
+ resend: 'Reenviar código',
163
+ resendIn: 'Reenviar em <b>{time}</b>',
164
+ validating: 'Validando...',
165
+ },
166
+ changeEmail: {
167
+ title: 'Alterar e-mail',
168
+ confirm: 'Alterar e-mail',
169
+ sentCodeTo: 'Enviamos um código de validação para o E-mail <b>{email}.</b>',
170
+ updateTitle: 'Atualizar E-mail de contato',
171
+ updateDescription: 'Informe o seu novo E-mail para manter seu contato atualizado.',
172
+ newEmail: 'Novo e-mail',
173
+ newEmailPlaceholder: 'Digite seu novo e-mail',
174
+ emailChanged: 'E-mail alterado com sucesso',
175
+ emailExistsError: 'E-mail já existente ou ocorreu um erro. Tente novamente mais tarde.',
176
+ },
177
+ changePhone: {
178
+ titleEdit: 'Alterar telefone',
179
+ titleAdd: 'Adicionar telefone',
180
+ sentCodeTo: 'Enviamos um código de validação para o número atual <b>{phone}.</b>',
181
+ confirm: 'Alterar telefone',
182
+ updateTitle: 'Atualizar telefone de contato',
183
+ addTitle: 'Adicionar telefone de contato',
184
+ updateDescription: 'Informe o novo número para manter seu contato atualizado.',
185
+ addDescription: 'Informe seu número de telefone para contato.',
186
+ newNumber: 'Novo número',
187
+ numberLabel: 'Número de telefone',
188
+ phoneChanged: 'Telefone alterado',
189
+ phoneAdded: 'Telefone adicionado',
190
+ invalidNumber: 'Número de telefone inválido',
191
+ saveError: 'Ocorreu um erro ao salvar o telefone. Tente novamente mais tarde.',
192
+ numberExistsError: 'Número já existente ou ocorreu um erro. Tente novamente mais tarde.',
193
+ },
194
+ security: {
195
+ title: 'Segurança',
196
+ googleLinkedTitle: 'Conta Google vinculada',
197
+ googleLinkedDescription:
198
+ 'Sua conta está vinculada ao Google. Se desvincular, use "Esqueci minha senha" para redefinir uma senha de acesso por e-mail.',
199
+ unlinking: 'Desvinculando...',
200
+ unlinkGoogle: 'Desvincular Google',
201
+ unlinkError: 'Erro ao desvincular conta Google.',
202
+ deleteAccountDescription:
203
+ 'Esta ação é permanente e não pode ser desfeita. Todos os seus dados, projetos e configurações serão removidos definitivamente.',
204
+ deleteMyAccount: 'Excluir minha conta',
205
+ },
206
+ token: {
207
+ title: 'Token da conta',
208
+ label: 'Token',
209
+ copySuccess: 'Token copiado com sucesso',
210
+ copyError: 'Não foi possível copiar o token',
211
+ info: 'Este token é exclusivo da sua conta e permite acesso à API. Não compartilhe com terceiros.',
212
+ docsLink: 'Acessar a documentação',
213
+ },
214
+ changePassword: {
215
+ title: 'Alterar senha',
216
+ currentPassword: 'Senha atual',
217
+ currentPasswordPlaceholder: 'Digite sua senha atual',
218
+ newPassword: 'Nova senha',
219
+ newPasswordPlaceholder: 'Digite sua nova senha',
220
+ submit: 'Salvar nova senha',
221
+ changeError: 'Erro ao alterar senha',
222
+ changedSuccess: 'Senha alterada com sucesso',
223
+ genericError: 'Erro ao alterar senha. Tente novamente.',
224
+ },
225
+ globalPreferences: {
226
+ title: 'Alterar preferências globais',
227
+ srDescription: 'Confirmação de alteração de preferências globais da conta',
228
+ warning:
229
+ 'Você está alterando configurações que impactam <b>todos os usuários da conta</b>. Deseja confirmar?',
230
+ },
231
+ configurations: {
232
+ title: 'Configurações',
233
+ },
234
+ },
235
+
236
+ // Nomes dos idiomas exibidos no seletor.
237
+ languages: {
238
+ 'pt-br': 'Português (Brasil)',
239
+ 'en-us': 'Inglês (EUA)',
240
+ 'es-es': 'Espanhol',
241
+ },
242
+ } as const;
243
+
244
+ export default messages;
@@ -0,0 +1,15 @@
1
+ import { Locale } from './config';
2
+
3
+ /**
4
+ * Normaliza um identificador de idioma vindo da API (`user.language`, ex.: `pt-br`, `en`, `es`)
5
+ * ou de um header BCP-47 (`pt-BR`, `en-US`, `es-419`) para um {@link Locale} suportado.
6
+ * Retorna `null` quando não reconhece o idioma.
7
+ */
8
+ export function normalizeLocale(input?: string | null): Locale | null {
9
+ if (!input) return null;
10
+ const value = input.trim().toLowerCase();
11
+ if (value.startsWith('pt')) return 'pt-br';
12
+ if (value.startsWith('es')) return 'es-es';
13
+ if (value.startsWith('en')) return 'en-us';
14
+ return null;
15
+ }
@@ -0,0 +1,77 @@
1
+ import 'server-only';
2
+
3
+ import { cookies, headers } from 'next/headers';
4
+ import { DEFAULT_LOCALE, Locale, LOCALE_COOKIE } from './config';
5
+ import { localeFromCountry } from './country-language';
6
+ import { normalizeLocale } from './normalize';
7
+
8
+ const AUTH_COOKIE = 'greatapps';
9
+ const GEO_HEADER = 'cf-ipcountry';
10
+ const DEFAULT_WHITELABEL_ID = 1;
11
+
12
+ interface JwtLocaleInfo {
13
+ id_wl?: number;
14
+ country?: string | null;
15
+ }
16
+
17
+ /** Lê id_wl e país do JWT sem custo de rede (best-effort). */
18
+ function decodeJwtLocaleInfo(token?: string | null): JwtLocaleInfo | null {
19
+ if (!token) return null;
20
+ try {
21
+ const payload = JSON.parse(atob(token.split('.')[1]));
22
+ return {
23
+ id_wl: payload?.id_wl != null ? Number(payload.id_wl) : undefined,
24
+ country: payload?.session?.location?.country ?? null,
25
+ };
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ export interface ResolveLocaleOptions {
32
+ /**
33
+ * Whitelabel id já resolvido (ex.: via `findWhitelabel()`), com prioridade sobre o JWT.
34
+ * Permite detectar domínio whitelabel mesmo para visitante não-logado.
35
+ */
36
+ whitelabelId?: number | null;
37
+ /** Idioma da conta (`user.language`), quando já conhecido pelo chamador. */
38
+ userLanguage?: string | null;
39
+ }
40
+
41
+ /**
42
+ * Resolve o locale da requisição (server-side). Ordem de prioridade:
43
+ *
44
+ * 1. **Whitelabel não-padrão** (`id_wl != 1`) → `pt` (por enquanto, whitelabels não são localizadas).
45
+ * 2. **Preferência da conta** (`userLanguage`), quando fornecida.
46
+ * 3. **Cookie `NEXT_LOCALE`** — cache da preferência ou escolha manual do visitante.
47
+ * 4. **Inferência por país** — JWT (`session.location.country`) ou header `CF-IPCountry`.
48
+ * 5. **Fallback** → {@link DEFAULT_LOCALE}.
49
+ */
50
+ export async function resolveLocale(options: ResolveLocaleOptions = {}): Promise<Locale> {
51
+ const cookieStore = await cookies();
52
+ const token = process.env.DUMMY_AUTH_TOKEN || cookieStore.get(AUTH_COOKIE)?.value;
53
+ const jwt = decodeJwtLocaleInfo(token);
54
+
55
+ // 1. Whitelabel não-padrão → português
56
+ const whitelabelId = options.whitelabelId ?? jwt?.id_wl ?? null;
57
+ if (whitelabelId != null && whitelabelId !== DEFAULT_WHITELABEL_ID) {
58
+ return 'pt-br';
59
+ }
60
+
61
+ // 2. Preferência explícita da conta
62
+ const fromUser = normalizeLocale(options.userLanguage);
63
+ if (fromUser) return fromUser;
64
+
65
+ // 3. Cookie persistido
66
+ const fromCookie = normalizeLocale(cookieStore.get(LOCALE_COOKIE)?.value);
67
+ if (fromCookie) return fromCookie;
68
+
69
+ // 4. Inferência por país (geo)
70
+ const headersList = await headers();
71
+ const country = jwt?.country ?? headersList.get(GEO_HEADER);
72
+ const fromGeo = localeFromCountry(country);
73
+ if (fromGeo) return fromGeo;
74
+
75
+ // 5. Fallback
76
+ return DEFAULT_LOCALE;
77
+ }
package/src/i18n.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Barril client-safe de i18n (`@greatapps/common/i18n`).
3
+ * NÃO exporta `resolveLocale` (server-only) — esse vem de `@greatapps/common/server`.
4
+ */
5
+ export { LOCALES, DEFAULT_LOCALE, LOCALE_COOKIE, isLocale } from './i18n/config';
6
+ export type { Locale } from './i18n/config';
7
+ export { normalizeLocale } from './i18n/normalize';
8
+ export { localeFromCountry } from './i18n/country-language';
9
+ export { getCommonMessages } from './i18n/messages';
10
+ export type { CommonMessages } from './i18n/messages';
@@ -5,6 +5,7 @@ import {
5
5
  ReactNode,
6
6
  useCallback,
7
7
  useContext,
8
+ useEffect,
8
9
  useMemo,
9
10
  } from "react";
10
11
  import { useQueryClient } from "@tanstack/react-query";
@@ -41,8 +42,10 @@ import {
41
42
  useCurrentAccount,
42
43
  ACCOUNT_QUERY_KEY,
43
44
  } from "../modules/accounts/hooks/current-account.hook";
44
- import { useWhitelabelUrls } from "./whitelabel.provider";
45
+ import { useWhitelabelUrls, useWhitelabel } from "./whitelabel.provider";
45
46
  import { useWlQueryKey } from "../hooks/useAuthQueryKey";
47
+ import { normalizeLocale } from "../i18n/normalize";
48
+ import { setLocaleCookie } from "../utils/intl/locale-cookie";
46
49
 
47
50
  interface AuthContextProps extends AuthState {
48
51
  login: (credentials: LoginRequest) => Promise<LoginResult>;
@@ -68,8 +71,17 @@ export function AuthProvider({ children }: AuthProviderProps) {
68
71
  const queryClient = useQueryClient();
69
72
  const setUserData = useSetUserData();
70
73
  const { accountsUrl, pagesUrl } = useWhitelabelUrls();
74
+ const { whitelabel } = useWhitelabel();
71
75
  const accountKey = useWlQueryKey([...ACCOUNT_QUERY_KEY]);
72
76
 
77
+ // Mantém o cookie NEXT_LOCALE alinhado à preferência da conta (login, sessão existente,
78
+ // atualização). Compartilhado entre subdomínios via domínio do whitelabel. A próxima
79
+ // navegação/requisição passa a resolver o locale pela preferência do usuário.
80
+ useEffect(() => {
81
+ const locale = normalizeLocale(user?.language);
82
+ if (locale) setLocaleCookie(locale, whitelabel?.domain);
83
+ }, [user?.language, whitelabel?.domain]);
84
+
73
85
  const { mutateAsync: loginMutate, isPending: isLogging } = useLogin();
74
86
  const { mutateAsync: registerMutate, isPending: isRegistering } =
75
87
  useRegister();
package/src/server.ts CHANGED
@@ -74,6 +74,8 @@ export {
74
74
  // Server Utils
75
75
  export { getClientInfoFromRequest } from './infra/utils/client-info';
76
76
  export { getUserContext } from './modules/auth/utils/get-user-context';
77
+ export { resolveLocale } from './i18n/resolve-locale';
78
+ export type { ResolveLocaleOptions } from './i18n/resolve-locale';
77
79
  export { requireValidSession } from './modules/auth/utils/require-valid-session';
78
80
  export { requireTokenNotExpired } from './modules/auth/utils/require-token-not-expired';
79
81
  export { buildWlOverride, buildWlOverrideFromJwt } from './modules/auth/utils/build-wl-override';