Skip to content

Commit d8de170

Browse files
author
Divyansh Singh
authored
feat(geo): add getDefaultLocaleList api in geo module [ROW-571] (#231)
* feat: add getDefaultLocaleList API in geo module Introduce a new function `getDefaultLocaleList` that retrieves the default locale for all countries from the i18nify-data source. * docs: replace non-breaking space with regular space in README and add documentation for getDefaultLocaleList API * Create hot-news-fly.md * feat: export getDefaultLocaleList from geo module
1 parent 0a7a897 commit d8de170

File tree

5 files changed

+289
-4
lines changed

5 files changed

+289
-4
lines changed

.changeset/hot-news-fly.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@razorpay/i18nify-js": patch
3+
---
4+
5+
feat: add getDefaultLocaleList api in geo module [ROW-571]

packages/i18nify-js/README.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ console.log(
234234
},
235235
{
236236
"type": "literal",
237-
"value": " "
237+
"value": " "
238238
},
239239
{
240240
"type": "integer",
@@ -293,7 +293,7 @@ console.log(
293293
},
294294
{
295295
"type": "literal",
296-
"value": " "
296+
"value": " "
297297
},
298298
{
299299
"type": "currency",
@@ -369,7 +369,7 @@ console.log(
369369
},
370370
{
371371
"type": "literal",
372-
"value": " "
372+
"value": " "
373373
},
374374
{
375375
"type": "currency",
@@ -743,7 +743,7 @@ getDefaultLocaleByCountry('XYZ').catch((err) => {
743743

744744
#### getByCountry
745745

746-
The getByCountry API offers a treasure trove of information about any country in the world. With a single query, you can uncover details like country names, languages, currencies, dial codes, timezones, and even links to their flags. Its a perfect tool for building apps that need geographic or cultural context.
746+
The getByCountry API offers a treasure trove of information about any country in the world. With a single query, you can uncover details like country names, languages, currencies, dial codes, timezones, and even links to their flags. It's a perfect tool for building apps that need geographic or cultural context.
747747

748748
##### Examples
749749

@@ -806,6 +806,38 @@ console.log(locales);
806806
*/
807807
```
808808

809+
#### getDefaultLocaleList() 🌐
810+
811+
The `getDefaultLocaleList` API provides a comprehensive mapping of country codes to their default locale codes. This function is particularly useful when you need to quickly identify the primary language/locale used in each country. It returns a promise that resolves to an object where each key is a country code, and its value is the default locale code for that country.
812+
813+
##### Examples
814+
815+
```javascript
816+
// Fetching default locales for all countries
817+
const defaultLocales = await getDefaultLocaleList();
818+
console.log(defaultLocales);
819+
/* Output:
820+
{
821+
"AF": "fa_AF", // Afghanistan's default locale
822+
"IN": "en_IN", // India's default locale
823+
"US": "en_US", // United States' default locale
824+
"JP": "ja_JP", // Japan's default locale
825+
// ... other countries and their default locales
826+
}
827+
*/
828+
829+
// Error handling
830+
try {
831+
const defaultLocales = await getDefaultLocaleList();
832+
} catch (error) {
833+
console.error(error.message);
834+
// Handles various error cases like:
835+
// - Network errors
836+
// - Invalid response format
837+
// - Empty response data
838+
}
839+
```
840+
809841
#### getLocaleByCountry
810842

811843
The getLocaleByCountry API is your multilingual compass, helping you discover all the supported locales for any country. Perfect for building apps that need to handle multiple language variants within a single country, it returns an array of locale codes that are officially used in the specified country.
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import getDefaultLocaleList from '../getDefaultLocaleList';
2+
3+
describe('getDefaultLocaleList', () => {
4+
beforeEach(() => {
5+
// Reset all mocks before each test
6+
jest.resetAllMocks();
7+
});
8+
9+
it('fetches default locales for all countries correctly', async () => {
10+
const mockResponse = {
11+
metadata_information: {
12+
AF: { default_locale: 'fa_AF' },
13+
IN: { default_locale: 'en_IN' },
14+
US: { default_locale: 'en_US' },
15+
GB: { default_locale: 'en_GB' },
16+
},
17+
};
18+
19+
global.fetch = jest.fn().mockImplementation(() =>
20+
Promise.resolve({
21+
ok: true,
22+
json: () => Promise.resolve(mockResponse),
23+
}),
24+
);
25+
26+
const defaultLocales = await getDefaultLocaleList();
27+
28+
expect(defaultLocales).toEqual({
29+
AF: 'fa_AF',
30+
IN: 'en_IN',
31+
US: 'en_US',
32+
GB: 'en_GB',
33+
});
34+
35+
expect(fetch).toHaveBeenCalledTimes(1);
36+
expect(fetch).toHaveBeenCalledWith(
37+
expect.stringContaining('/country/metadata/data.json'),
38+
);
39+
});
40+
41+
it('handles API errors', async () => {
42+
global.fetch = jest.fn().mockImplementation(() =>
43+
Promise.resolve({
44+
ok: false,
45+
status: 404,
46+
}),
47+
);
48+
49+
await expect(getDefaultLocaleList()).rejects.toThrow(
50+
'An error occurred while fetching country metadata. The error details are: HTTP error! status: 404.',
51+
);
52+
});
53+
54+
it('handles network errors', async () => {
55+
global.fetch = jest
56+
.fn()
57+
.mockImplementation(() => Promise.reject(new Error('Network error')));
58+
59+
await expect(getDefaultLocaleList()).rejects.toThrow(
60+
'An error occurred while fetching country metadata. The error details are: Network error.',
61+
);
62+
});
63+
64+
it('handles invalid response format', async () => {
65+
global.fetch = jest.fn().mockImplementation(() =>
66+
Promise.resolve({
67+
ok: true,
68+
json: () => Promise.resolve({}),
69+
}),
70+
);
71+
72+
await expect(getDefaultLocaleList()).rejects.toThrow(
73+
'An error occurred while fetching country metadata. The error details are: Invalid response format: missing metadata_information.',
74+
);
75+
});
76+
77+
it('handles empty metadata_information', async () => {
78+
global.fetch = jest.fn().mockImplementation(() =>
79+
Promise.resolve({
80+
ok: true,
81+
json: () => Promise.resolve({ metadata_information: {} }),
82+
}),
83+
);
84+
85+
await expect(getDefaultLocaleList()).rejects.toThrow(
86+
'An error occurred while fetching country metadata. The error details are: No default locales found in the response.',
87+
);
88+
});
89+
90+
it('handles null default_locale values', async () => {
91+
const mockResponse = {
92+
metadata_information: {
93+
AF: { default_locale: null },
94+
IN: { default_locale: 'en_IN' },
95+
},
96+
};
97+
98+
global.fetch = jest.fn().mockImplementation(() =>
99+
Promise.resolve({
100+
ok: true,
101+
json: () => Promise.resolve(mockResponse),
102+
}),
103+
);
104+
105+
const defaultLocales = await getDefaultLocaleList();
106+
107+
expect(defaultLocales).toEqual({
108+
IN: 'en_IN',
109+
});
110+
});
111+
112+
it('handles undefined default_locale values', async () => {
113+
const mockResponse = {
114+
metadata_information: {
115+
AF: { default_locale: undefined },
116+
IN: { default_locale: 'en_IN' },
117+
},
118+
};
119+
120+
global.fetch = jest.fn().mockImplementation(() =>
121+
Promise.resolve({
122+
ok: true,
123+
json: () => Promise.resolve(mockResponse),
124+
}),
125+
);
126+
127+
const defaultLocales = await getDefaultLocaleList();
128+
129+
expect(defaultLocales).toEqual({
130+
IN: 'en_IN',
131+
});
132+
});
133+
134+
it('handles malformed JSON response', async () => {
135+
global.fetch = jest.fn().mockImplementation(() =>
136+
Promise.resolve({
137+
ok: true,
138+
json: () => Promise.reject(new Error('Unexpected token <')),
139+
}),
140+
);
141+
142+
await expect(getDefaultLocaleList()).rejects.toThrow(
143+
'An error occurred while fetching country metadata. The error details are: Unexpected token <.',
144+
);
145+
});
146+
147+
it('handles timeout errors', async () => {
148+
global.fetch = jest
149+
.fn()
150+
.mockImplementation(() =>
151+
Promise.reject(new Error('Timeout of 5000ms exceeded')),
152+
);
153+
154+
await expect(getDefaultLocaleList()).rejects.toThrow(
155+
'An error occurred while fetching country metadata. The error details are: Timeout of 5000ms exceeded.',
156+
);
157+
});
158+
159+
it('handles CORS errors', async () => {
160+
global.fetch = jest
161+
.fn()
162+
.mockImplementation(() =>
163+
Promise.reject(
164+
new Error(
165+
'Failed to fetch: NetworkError when attempting to fetch resource.',
166+
),
167+
),
168+
);
169+
170+
await expect(getDefaultLocaleList()).rejects.toThrow(
171+
'An error occurred while fetching country metadata. The error details are: Failed to fetch: NetworkError when attempting to fetch resource.',
172+
);
173+
});
174+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { withErrorBoundary } from '../../common/errorBoundary';
2+
import { I18NIFY_DATA_SOURCE } from '../shared';
3+
4+
/**
5+
* Interface defining the structure of country metadata response
6+
* from the i18nify-data source
7+
*/
8+
interface CountryMetadata {
9+
metadata_information: {
10+
[key: string]: {
11+
default_locale: string;
12+
};
13+
};
14+
}
15+
16+
/**
17+
* Retrieves the default locale for all countries
18+
*
19+
* This function makes a network request to central i18nify-data source and
20+
* returns a promise for the default locales of all countries
21+
*
22+
* @returns {Promise<Record<string, string>>} Promise object containing default locale information for all countries
23+
* @throws {Error} If the API request fails or returns invalid data
24+
*/
25+
const getDefaultLocaleList = async (): Promise<Record<string, string>> => {
26+
try {
27+
// Make HTTP request to fetch country metadata
28+
const response = await fetch(
29+
`${I18NIFY_DATA_SOURCE}/country/metadata/data.json`,
30+
);
31+
32+
// Check if the response is successful
33+
if (!response.ok) {
34+
throw new Error(`HTTP error! status: ${response.status}`);
35+
}
36+
37+
// Parse the JSON response and type it as CountryMetadata
38+
const data = (await response.json()) as CountryMetadata;
39+
40+
// Validate that the response contains the required metadata_information
41+
if (!data?.metadata_information) {
42+
throw new Error('Invalid response format: missing metadata_information');
43+
}
44+
45+
// Initialize an empty object to store country code to default locale mapping
46+
const defaultLocales: Record<string, string> = {};
47+
48+
// Iterate through each country's metadata and extract default locale
49+
// Skip countries that don't have a default_locale defined
50+
Object.entries(data.metadata_information).forEach(([code, info]) => {
51+
if (info?.default_locale) {
52+
defaultLocales[code] = info.default_locale;
53+
}
54+
});
55+
56+
// Validate that we found at least one default locale
57+
if (Object.keys(defaultLocales).length === 0) {
58+
throw new Error('No default locales found in the response');
59+
}
60+
61+
return defaultLocales;
62+
} catch (err) {
63+
// Wrap any errors in a descriptive message while preserving the original error details
64+
throw new Error(
65+
`An error occurred while fetching country metadata. The error details are: ${err instanceof Error ? err.message : String(err)}.`,
66+
);
67+
}
68+
};
69+
70+
// Export the function wrapped with error boundary for consistent error handling
71+
export default withErrorBoundary<typeof getDefaultLocaleList>(
72+
getDefaultLocaleList,
73+
);

packages/i18nify-js/src/modules/geo/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export { default as getDefaultLocaleByCountry } from './getDefaultLocaleByCountr
1010
export { default as getLocaleList } from './getLocaleList';
1111
export { default as getLocaleByCountry } from './getLocaleByCountry';
1212
export { default as getCityByZipcode } from './getCityByZipcode';
13+
export { default as getDefaultLocaleList } from './getDefaultLocaleList';

0 commit comments

Comments
 (0)