In previous posts I wrote about using both the Context Provider Pattern and a Token Provider to manage authorization in GraphQL. To a similar effect we can using these patterns to provide localization context to our resolvers and their backing services.

Like before we are going to setup a new injectable service with inversify. In this case it will be the LocalizationProvider.

const ILocalizationProviderType = Symbol.for("ILocalizationProvider");
interface ILocalizationProvider {
    market: "US" | "CA";
    fromRequest: (request: Request) => void;
    isUs: boolean;
}

We are following the same pattern from previous posts of have a fromRequest method take in an express (or any other framework) request object and hydrate the provider. In our case we are going to look for specific parts of the request to understand how to set the market for localization.

@injectable()
class LocalizationProvider implements ILocalizationProvider {
    private _market: "US" | "CA";
    fromRequest(request: Request) {
        const marketHeader = request.get("Market");
        if(marketHeader) {
            this._market = marketHeader === "CA" ? "CA" : "US";
        }
        else {
            const url = request.url;
            if(tldExists(url)) {
                const publicSuffix = getPublicSuffix(url);
                this._market = publicSuffix === "ca" ? "CA" : "US";
            }
            else {
                this._market = "US";
            }
        }
    }
    get market() {
        return this._market;
    }
    get isUS() {
        return this._market === "US";
    }
}

Your strategy for determining which localization markets you support and how you want to use the request to determine the current context is up to you. In this case we are first checking for the presence of a header and otherwise check the request TLD using tldjs and determine simply whether the request is to a ca domain or not.

Like in previous examples you would then need to register your provider and pass the request during the context callback.

container.bind<ILocalizationProvider>(ILocalizationProviderType).to(LocalizationProvider).inRequestScope();

...

interface IContextProvider {
    bookingService: IBookingService;
    tokenProvider: ITokenProvider;
    localizationProvider: ILocalizationProvider;
}

...

@injectable()
class ContextProvider implements IContextProvider {
    @inject(IBookingServiceType)
    public bookingService: IBookingService;
    @inject(ITokenProviderType)
    public tokenProvider: ITokenProvider;
    @inject(ILocalizationProviderType)
    public localizationProvider: ILocalizationProvider;
}

...

app.use(
    '/graphql',
    cors(),
    bodyParser.json(),
    graphqlExpress((request) => (
        { 
            schema, 
            context: () => {
                const context = container.get<IContextProvider>(IContextProviderType);
                context.tokenProvider.fromRequest(request);
                context.localizationProvider.fromRequest(request);
                return context;
            }
        }
    ))
);

Now you can use the localization provider in your resolvers or services to return the proper values.

export default {
    Product: {
        price: (parent: ProductDTO, args, context: IContextProvider) => {
            return context.localizationProvider.isUS
                ? parent.priceUSD
                : parent.priceCAD;
        }
    }
}

Thanks for reading and happy coding!