Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i18n: pass locale to shop-api #104

Open
redaready opened this issue Jun 12, 2023 · 12 comments
Open

i18n: pass locale to shop-api #104

redaready opened this issue Jun 12, 2023 · 12 comments

Comments

@redaready
Copy link
Contributor

is there a way to pass the locale value of "extractLang(request.headers.get('accept-language'), request.url)" to shop-api call?

i have tried something like this, but a error raised: "Internal server error: Reading locale outside of context."

const executeRequest = async (options: Options) => {
	const locale = getLocale();
	const httpResponse = await fetch(`${ENV_VARIABLES.VITE_VENDURE_PUBLIC_URL}/?languageCode=${locale}`, options);
	return await extractTokenAndData(httpResponse);
};
@gioboa
Copy link
Collaborator

gioboa commented Jun 12, 2023

I will investigate in it

@redaready
Copy link
Contributor Author

thanks gioboa, i have tried add "locale" to the option of requester, it works, but i don't think it's the best way to do the job because we are obliged to add locale option to many api calls...

diff --git a/src/providers/collections/collections.ts b/src/providers/collections/collections.ts
index 3034f63..2802a6d 100644
--- a/src/providers/collections/collections.ts
+++ b/src/providers/collections/collections.ts
@@ -1,9 +1,9 @@
 import gql from 'graphql-tag';
-import { sdk } from '~/graphql-wrapper';
 import { Collection } from '~/generated/graphql';
+import { sdk } from '~/graphql-wrapper';
 
-export const getCollections = async () => {
-	return await sdk.collections().then((res) => res.collections.items as Collection[]);
+export const getCollections = async (locale: string) => {
+	return await sdk.collections(undefined, { locale: locale }).then((res) => res.collections.items as Collection[]);
 };
 
 export const getCollectionBySlug = async (slug: string) => {
diff --git a/src/routes/layout.tsx b/src/routes/layout.tsx
index 9d8e4c2..eb14bfc 100644
--- a/src/routes/layout.tsx
+++ b/src/routes/layout.tsx
@@ -19,8 +19,9 @@ import { extractLang } from '~/utils/i18n';
 import Footer from '../components/footer/footer';
 import Header from '../components/header/header';
 
-export const useCollectionsLoader = routeLoader$(async () => {
-	return await getCollections();
+export const useCollectionsLoader = routeLoader$(async (requestEvent) => {
+	const locale = extractLang(requestEvent.headers.get('accept-language'), requestEvent.url.toString())
+	return await getCollections(locale);
 });
 
 export const useAvailableCountriesLoader = routeLoader$(async () => {
diff --git a/src/utils/api.ts b/src/utils/api.ts
index f03dc54..ee28204 100644
--- a/src/utils/api.ts
+++ b/src/utils/api.ts
@@ -1,7 +1,7 @@
 import { server$ } from '@builder.io/qwik-city';
 import { isBrowser } from '@builder.io/qwik/build';
 import { DocumentNode, print } from 'graphql/index';
-import { AUTH_TOKEN, HEADER_AUTH_TOKEN_KEY } from '~/constants';
+import { AUTH_TOKEN, DEFAULT_LOCALE, HEADER_AUTH_TOKEN_KEY } from '~/constants';
 import { ENV_VARIABLES } from '~/env';
 import { getCookie, setCookie } from '.';
 
@@ -12,22 +12,25 @@ type Options = { method: string; headers: Record<string, string>; body: string }
 export const requester = async <R, V>(
 	doc: DocumentNode,
 	vars?: V,
-	options: { token?: string } = { token: '' }
+	options: { token?: string, locale?: string } = { token: '' }
 ): Promise<R> => {
-	return execute<R, V>({ query: print(doc), variables: vars }, options.token);
+	return execute<R, V>({ query: print(doc), variables: vars }, options.token, options.locale || DEFAULT_LOCALE);
 };
 
 const execute = async <R, V = Record<string, any>>(
 	body: ExecuteProps<V>,
-	authToken: string = ''
+	authToken: string = '',
+	locale: string
 ): Promise<R> => {
-	const options = { method: 'POST', headers: createHeaders(), body: JSON.stringify(body) };
+	const options = {
+		method: 'POST', headers: createHeaders(), body: JSON.stringify(body)
+	};
 	if (authToken !== '') {
 		options.headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${authToken}` };
 	}
 	const response: ResponseProps<R> = isBrowser
-		? await executeOnTheServer(options)
-		: await executeRequest(options);
+		? await executeOnTheServer(options, locale)
+		: await executeRequest(options, locale);
 
 	if (isBrowser && response.token) {
 		setCookie(AUTH_TOKEN, response.token, 365);
@@ -45,10 +48,10 @@ const createHeaders = () => {
 	return headers;
 };
 
-const executeOnTheServer = server$(async (options: Options) => executeRequest(options));
+const executeOnTheServer = server$(async (options: Options, locale: string) => executeRequest(options, locale));
 
-const executeRequest = async (options: Options) => {
-	const httpResponse = await fetch(ENV_VARIABLES.VITE_VENDURE_PUBLIC_URL, options);
+const executeRequest = async (options: Options, locale?: string) => {
+	const httpResponse = await fetch(`${ENV_VARIABLES.VITE_VENDURE_PUBLIC_URL}/?languageCode=${locale}`, options);
 	return await extractTokenAndData(httpResponse);
 };
 

@giovramirez
Copy link
Contributor

@redaready, have you tried setting the locale in a Cookie just like it's done with the Vendure token?

You might be facing "Internal server error: Reading locale outside of context." due to the locale is set here. So probably the getLocale() function is called before the locale is set.

@redaready
Copy link
Contributor Author

with some help of sourcegraph cody:

That error means you are calling getLocale() outside of a Qwik component or route. getLocale() relies on Qwik's locale context, so it can only be called within a Qwik component/route.

Some options to fix this:

Pass the locale as an argument to the function instead of calling getLocale():

function doSomething(locale: string) {
  // Use locale here instead of calling getLocale()
}

Wrap the call in a Qwik component:

component$(() => {
  const locale = getLocale();
  // Use locale here
});

Use a Qwik route and access the locale from the request event:

export const onRequest: RequestHandler = ({ locale }) => {
  // Use locale here
};

Store the locale in a variable outside of the Qwik context and pass that in:

let locale;

component$(() => {
  locale = getLocale();
});

function doSomething() {
  // Use locale here instead of calling getLocale()
}

Hope this helps resolve your error! Let me know if you have any other questions.

@redaready
Copy link
Contributor Author

@giovramirez i think locale is a little bit diffrent with token, token is generated by vendure and then stored in cookie, locale on other hand, we must pass it to sdk calls at first invocations (ex: useCollectionsLoader and useAvailableCountriesLoader etc)... so i think we have no other choice than pass locale parameter to many sdk calls :-(

@giovramirez
Copy link
Contributor

You're right @redaready. Besides, we can't use a Cookie since most of the calls are executed on the server, so it's not an option to get the locale from Cookie.

We have this interceptor where we can get the locale, store it outside of the Qwik context and use it globally within the app. However, I think for better readability of the codebase your approach of passing the locale parameter to every sdk looks better.

@gioboa any thoughts on this matter?

@gioboa
Copy link
Collaborator

gioboa commented Jun 23, 2023

I need to investigate into the problem, I will look at it in few days. You are more then welcome to suggest a solution with a PR

@prasmalla
Copy link
Contributor

my approach was a global config variable for currencyCode and use it here

const executeRequest = async (options: Options) => {
	const httpResponse = await fetch(
		`${ENV_VARIABLES.VITE_VENDURE_PUBLIC_URL}?currencyCode=${getCurrencyCode()}`,
		options
	);
	return await extractTokenAndData(httpResponse);
};

@gioboa
Copy link
Collaborator

gioboa commented Dec 14, 2023

This sounds reasonable to me 👍
Thanks for sharing

@redaready
Copy link
Contributor Author

can we change locale per request in this way? sometimes storefront has a Language Selector component, we need to get locale value from request context(cookie, path, or header) to call api. i think the locale value proprity chain could be

  1. user selection on web interface Language Selector (locale value persisted in cookie ou path, cookie prefered)
  2. user preference setting (stocked on server side, not in vendure now)
  3. server side app globle deflaut config (as @prasmalla said)
  4. browser language header

@gioboa
Copy link
Collaborator

gioboa commented Dec 15, 2023

Yep, if we change the path with a variable we can do whatever we want

@prasmalla
Copy link
Contributor

@redaready we did as you mentioned storing the user selection in a cookie and updating the client-side global config on changes. vendure backend already supports it and there was an issue here that came up with this approach which has been fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants