Skip to content

Commit 401e688

Browse files
committed
Add full Spanish (es-ES) translation support for Asgardeo JavaScript SDK
1 parent 66838b8 commit 401e688

5 files changed

Lines changed: 257 additions & 12 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@asgardeo/i18n": minor
3+
---
4+
5+
Added to the Asgardeo JavaScript SDK support for Spanish (Español - es-ES). Overall accessibility for Spanish-speaking users.

‎CONTRIBUTING.md‎

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,11 @@ export {default as fr_FR} from './fr-FR';
239239
Build the package and test the new language in a sample application to ensure all translations are working correctly:
240240

241241
```bash
242-
pnpm build --filter @asgardeo/i18n
242+
pnpm nx build @asgardeo/i18n
243243
```
244244

245+
> **Note:** This repo uses [Nx](https://nx.dev/), so build a single package with the Nx syntax above. Don't use `pnpm build --filter @asgardeo/i18n` — Nx doesn't recognize `--filter` and passes it to `tsc`, which fails with `error TS5023: Unknown compiler option '--filter'`.
246+
245247
To test your new language translation, you have two options:
246248

247249
##### Option 1: Using a symlink
@@ -306,9 +308,50 @@ pnpm install
306308
# The sample will automatically use your local i18n package changes
307309
```
308310

311+
The samples reference the workspace packages via `workspace:*`, so `pnpm install` symlinks them to your local `packages/*` and your changes are picked up without any extra configuration.
312+
313+
> [!IMPORTANT]
314+
> After changing translation files, rebuild the i18n package so the sample (which imports the built `dist/`, not the source) sees your changes:
315+
>
316+
> ```bash
317+
> npx nx build @asgardeo/i18n
318+
> ```
319+
320+
###### Configure the sample with a `.env.local` file
321+
322+
The sample apps don't ship with any Asgardeo credentials — they read their configuration from environment variables at runtime (e.g. `import.meta.env.VITE_ASGARDEO_BASE_URL`). These values are specific to **your** Asgardeo organization and application, so they must not be committed. For that reason, `.env.local` is git-ignored (only a `.env.local.example` template is committed), and you need to create your own before the sample can connect to Asgardeo.
323+
324+
Vite automatically loads `.env.local` on `pnpm dev` and exposes every `VITE_`-prefixed variable to the app. Without this file, the base URL and client ID are `undefined` and sign-in/sign-up won't work.
325+
326+
1. Copy the template:
327+
328+
```bash
329+
cp .env.local.example .env.local
330+
```
331+
332+
2. Fill in the values from your Asgardeo application (Console → **Applications → your app → Protocol / Info**):
333+
334+
```bash
335+
VITE_ASGARDEO_BASE_URL='https://api.asgardeo.io/t/<your_organization_name>'
336+
VITE_ASGARDEO_CLIENT_ID='<client_id>'
337+
```
338+
339+
3. Start the dev server (from the sample directory):
340+
341+
```bash
342+
pnpm dev
343+
```
344+
345+
###### Explore the running app and confirm your translations
346+
347+
Open the URL the dev server prints (`teamspace-react` runs on `https://localhost:[host-number]`. If you get a warning on the site just accept the self-signed cert warning the first time) and walk through the auth screens.
348+
349+
> [!NOTE]
350+
> In the embedded sign-in/sign-up flows, most in-form text — headings, subtitles, input field labels, and the form's own submit/social buttons — is served by the Asgardeo server, not your bundle, so it stays in English regardless of your translation. Your bundle reliably translates error/validation messages and standalone SDK buttons (like the Sign In / Sign Up call-to-action buttons). Getting that server-driven text into your language is something you'll need to explore separately in the Asgardeo Console (its text/branding and flow settings) while testing.
351+
309352
##### Testing with AsgardeoProvider
310353

311-
Once you have your testing environment set up (using either option above), configure the AsgardeoProvider to use your new language:
354+
Once you have your testing environment set up (using either option above), configure the AsgardeoProvider to use your new language. The following steps take place in `samples/teamspace-react/src/main.tsx`:
312355

313356
- **Import your new language bundle**
314357

@@ -340,6 +383,9 @@ For more details on the i18n preferences interface, see the [`I18nPreferences`](
340383

341384
- **Verify the translations**
342385

386+
> [!NOTE]
387+
> This checklist is aspirational — it assumes every string comes from your bundle. As noted under [Explore the running app](#explore-the-running-app-and-confirm-your-translations), most in-form auth text is server-driven and won't reflect your bundle, so don't expect "all UI text" to change. Focus verification on the bundle-driven strings (errors, validation, standalone SDK buttons, post-login UI).
388+
343389
- Navigate through your application's authentication flows
344390
- Check that all UI text appears in your new language
345391
- Verify that buttons, labels, error messages, and other UI elements display the correct translations

‎packages/i18n/src/constants/TranslationBundleConstants.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* under the License.
1717
*/
1818

19-
import {en_US} from '../translations';
19+
import {en_US, es_ES} from '../translations';
2020

2121
/**
2222
* Constants related to internationalization (i18n) translation bundles.
@@ -39,8 +39,9 @@ const TranslationBundleConstants: {
3939
*
4040
* Current default locales:
4141
* - `en-US` - English (United States)
42+
* - `es-ES` - Spanish (Spain)
4243
*/
43-
DEFAULT_LOCALES: [en_US.metadata.localeCode],
44+
DEFAULT_LOCALES: [en_US.metadata.localeCode, es_ES.metadata.localeCode],
4445

4546
/**
4647
* Default locale code used as fallback when no specific locale is provided.
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/**
2+
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
/* eslint-disable sort-keys */
20+
/* eslint-disable @typescript-eslint/naming-convention */
21+
22+
import { I18nTranslations, I18nMetadata, I18nBundle } from '../models/i18n';
23+
24+
const translations: I18nTranslations = {
25+
/* |---------------------------------------------------------------| */
26+
/* | Elements | */
27+
/* |---------------------------------------------------------------| */
28+
29+
/* Buttons */
30+
'elements.buttons.signin.text': 'Iniciar sesión',
31+
'elements.buttons.signout.text': 'Cerrar sesión',
32+
'elements.buttons.signup.text': 'Registrarse',
33+
'elements.buttons.submit.text': 'Continuar',
34+
'elements.buttons.facebook.text': 'Continuar con Facebook',
35+
'elements.buttons.google.text': 'Continuar con Google',
36+
'elements.buttons.github.text': 'Continuar con GitHub',
37+
'elements.buttons.microsoft.text': 'Continuar con Microsoft',
38+
'elements.buttons.linkedin.text': 'Continuar con LinkedIn',
39+
'elements.buttons.ethereum.text': 'Continuar con Iniciar sesión con Ethereum',
40+
'elements.buttons.smsotp.text': 'Continuar con SMS OTP',
41+
'elements.buttons.multi.option.text': 'Continuar con {connection}',
42+
'elements.buttons.social.text': 'Continuar con {connection}',
43+
44+
/* Display */
45+
'elements.display.divider.or_separator': 'O',
46+
'elements.display.copyable_text.copy': 'Copiar',
47+
'elements.display.copyable_text.copied': 'Copiado!',
48+
49+
/* Fields */
50+
'elements.fields.generic.placeholder': 'Ingresa tu {field}',
51+
'elements.fields.username.label': 'Nombre de usuario',
52+
'elements.fields.username.placeholder': 'Ingresa tu nombre de usuario',
53+
'elements.fields.password.label': 'Contraseña',
54+
'elements.fields.password.placeholder': 'Ingresa tu Contraseña',
55+
'elements.fields.first_name.label': 'Nombre',
56+
'elements.fields.first_name.placeholder': 'Ingresa tu nombre',
57+
'elements.fields.last_name.label': 'Apellido',
58+
'elements.fields.last_name.placeholder': 'Ingresa tu apellido',
59+
'elements.fields.email.label': 'Correo electrónico',
60+
'elements.fields.email.placeholder': 'Correo electrónico',
61+
'elements.fields.organization.name.label': 'Nombre de la organización',
62+
'elements.fields.organization.handle.label': 'Identificador de la organización',
63+
'elements.fields.organization.description.label': 'Descripción de la organización',
64+
'elements.fields.organization.select.label': 'Seleccionar organización',
65+
'elements.fields.organization.select.placeholder': 'Elige una organización',
66+
67+
/* Validation */
68+
'validations.required.field.error': 'Este campo es obligatorio',
69+
70+
/* |---------------------------------------------------------------| */
71+
/* | Widgets | */
72+
/* |---------------------------------------------------------------| */
73+
74+
/* Base Sign In */
75+
'signin.heading': 'Iniciar sesión',
76+
'signin.subheading': 'Bienvenido de nuevo! Por favor, inicia sesión para continuar.',
77+
78+
/* Base Sign Up */
79+
'signup.heading': 'Regístrate',
80+
'signup.subheading': 'Crea una nueva cuenta para empezar.',
81+
82+
/* Email OTP */
83+
'email.otp.heading': 'Verificación de OTP',
84+
'email.otp.subheading': 'Ingresa el código que te enviamos a tu correo electrónico.',
85+
'email.otp.buttons.submit.text': 'Continuar',
86+
87+
/* Identifier First */
88+
'identifier.first.heading': 'Iniciar sesión',
89+
'identifier.first.subheading': 'Ingresa tu nombre de usuario o dirección de correo.',
90+
'identifier.first.buttons.submit.text': 'Continuar',
91+
92+
/* SMS OTP */
93+
'sms.otp.heading': 'Verificación de OTP',
94+
'sms.otp.subheading': 'Ingresa el código que te enviaron a tu número de teléfono.',
95+
'sms.otp.buttons.submit.text': 'Continuar',
96+
97+
/* TOTP */
98+
'totp.heading': 'Verifica tu identidad',
99+
'totp.subheading': 'Introduce el código de tu aplicación de autenticación.',
100+
'totp.buttons.submit.text': 'Continuar',
101+
102+
/* Username Password */
103+
'username.password.heading': 'Iniciar sesión',
104+
'username.password.subheading': 'Ingresa tu usuario y contraseña para continuar.',
105+
'username.password.buttons.submit.text': 'Continuar',
106+
107+
/* Passkeys */
108+
'passkey.button.use': 'Iniciar sesión con clave de acceso',
109+
'passkey.signin.heading': 'Iniciar sesión con clave de acceso',
110+
'passkey.register.heading': 'Registrar clave de acceso',
111+
'passkey.register.description': 'Crea una clave de acceso para iniciar sesión en tu cuenta de manera segura sin contraseña.',
112+
113+
/* |---------------------------------------------------------------| */
114+
/* | User Profile | */
115+
/* |---------------------------------------------------------------| */
116+
117+
'user.profile.heading': 'Perfil',
118+
'user.profile.update.generic.error': 'Ocurrió un error al actualizar tu perfil. Por favor, inténtalo de nuevo.',
119+
120+
/* |---------------------------------------------------------------| */
121+
/* | Organization Switcher | */
122+
/* |---------------------------------------------------------------| */
123+
124+
'organization.switcher.switch.organization': 'Cambiar de organización',
125+
'organization.switcher.loading.placeholder.organizations': 'Cargando organizaciones...',
126+
'organization.switcher.members': 'miembros',
127+
'organization.switcher.member': 'miembro',
128+
'organization.switcher.create.organization': 'Crear organización',
129+
'organization.switcher.manage.organizations': 'Administrar organizaciones',
130+
'organization.switcher.buttons.manage.text': 'Administrar',
131+
'organization.switcher.organizations.heading': 'Organizaciones',
132+
'organization.switcher.buttons.switch.text': 'Cambiar',
133+
'organization.switcher.no.access': 'Sin Acceso',
134+
'organization.switcher.status.label': 'Estatus:',
135+
'organization.switcher.showing.count': 'Mostrando {showing} de {total} organizaciones',
136+
'organization.switcher.buttons.refresh.text': 'Actualizar',
137+
'organization.switcher.buttons.load_more.text': 'Cargar más organizaciones',
138+
'organization.switcher.loading.more': 'Cargando...',
139+
'organization.switcher.no.organizations': 'No se encontraron organizaciones',
140+
'organization.switcher.error.prefix': 'Error:',
141+
142+
'organization.profile.heading': 'Perfil de la organización',
143+
'organization.profile.loading': 'Cargando organización...',
144+
'organization.profile.error': 'No se pudo cargar la organización',
145+
146+
'organization.create.heading': 'Crear organización',
147+
'organization.create.buttons.create_organization.text': 'Crear organización',
148+
'organization.create.buttons.create_organization.loading.text': 'Creando...',
149+
'organization.create.buttons.cancel.text': 'Cancelar',
150+
151+
/* |---------------------------------------------------------------| */
152+
/* | Messages | */
153+
/* |---------------------------------------------------------------| */
154+
155+
'messages.loading.placeholder': 'Cargando...',
156+
157+
/* |---------------------------------------------------------------| */
158+
/* | Errors | */
159+
/* |---------------------------------------------------------------| */
160+
161+
'errors.heading': 'Error',
162+
'errors.signin.components.not.available': 'El formulario de inicio de sesión no está disponible en este momento. Por favor, inténtalo de nuevo más tarde.',
163+
'errors.signin.initialization': 'Ocurrió un error al iniciar. Por favor, inténtalo de nuevo más tarde.',
164+
'errors.signin.flow.failure': 'Ocurrió un error durante el proceso de inicio de sesión. Por favor, inténtalo de nuevo más tarde.',
165+
'errors.signin.flow.completion.failure':
166+
'Ocurrió un error al completar el proceso de inicio de sesión. Por favor, inténtalo de nuevo más tarde.',
167+
'errors.signin.flow.passkeys.failure': 'Ocurrió un error al iniciar sesión con clave de acceso. Por favor, inténtalo de nuevo más tarde.',
168+
'errors.signin.flow.passkeys.completion.failure':
169+
'Ocurrió un error al completar el inicio de sesión con claves de acceso. Por favor, intenta de nuevo más tarde.',
170+
'errors.signin.timeout': 'Se terminó el tiempo para completar el paso.',
171+
172+
'errors.signup.initialization': 'Ocurrió un error al iniciar. Por favor, inténtalo de nuevo más tarde.',
173+
'errors.signup.flow.failure': 'Ocurrió un error durante el proceso de registro. Por favor, inténtalo de nuevo más tarde.',
174+
'errors.signup.flow.initialization.failure':
175+
'Ocurrió un error al iniciar el proceso de registro. Por favor, inténtalo de nuevo más tarde.',
176+
'errors.signup.components.not.available': 'El formulario de registro no está disponible en este momento. Por favor, inténtalo de nuevo más tarde.',
177+
};
178+
179+
const metadata: I18nMetadata = {
180+
localeCode: 'es-ES',
181+
countryCode: 'ES',
182+
languageCode: 'es',
183+
displayName: 'Español (España)',
184+
direction: 'ltr',
185+
};
186+
187+
const es_ES: I18nBundle = {
188+
metadata,
189+
translations,
190+
};
191+
192+
export default es_ES;

‎packages/i18n/src/translations/index.ts‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
* under the License.
1717
*/
1818

19-
export {default as en_US} from './en-US';
20-
export {default as fr_FR} from './fr-FR';
21-
export {default as te_IN} from './te-IN';
22-
export {default as hi_IN} from './hi-IN';
23-
export {default as pt_BR} from './pt-BR';
24-
export {default as pt_PT} from './pt-PT';
25-
export {default as ta_IN} from './ta-IN';
26-
export {default as si_LK} from './si-LK';
19+
export { default as es_ES } from './es-ES';
20+
export { default as en_US } from './en-US';
21+
export { default as fr_FR } from './fr-FR';
22+
export { default as te_IN } from './te-IN';
23+
export { default as hi_IN } from './hi-IN';
24+
export { default as pt_BR } from './pt-BR';
25+
export { default as pt_PT } from './pt-PT';
26+
export { default as ta_IN } from './ta-IN';
27+
export { default as si_LK } from './si-LK';

0 commit comments

Comments
 (0)