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

Localization #134

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ node_modules
/.mf
.env
/app/styles/app.css
.vscode
.vscode

/app/routes/$lang/*
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[Hydrogen 2.0 Gameplan Doc](https://docs.google.com/document/d/1iEerwYgs30rVDJi5JbfxCJf2K-boAwGOODmDFCNQDmY/edit#) _(High level roadmap)_

[Github Project for Demo Store & H2](https://github.com/orgs/Shopify/projects/5093/views/2) _(Tickets, etc.)_
[Github Project for Demo Store & H2](https://github.com/orgs/Shopify/projects/5093/views/1) _(Tickets, etc.)_

[Oxygen Deployment](https://h2-demo-store-f1f4fa724b7467f41f07.o2.myshopify.dev/)

Expand Down
4 changes: 2 additions & 2 deletions app/components/CartDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ function CartCheckoutActions({checkoutUrl}: {checkoutUrl: string}) {
<>
<div className="grid gap-4">
{checkoutUrl ? (
<Link to={checkoutUrl} prefetch="intent" target="_self">
<a href={checkoutUrl} target="_self">
<Button as="span" width="full">
Continue to Checkout
</Button>
</Link>
</a>
) : null}
{/* TODO: Shop Pay */}
{/* <CartShopPayButton /> */}
Expand Down
135 changes: 54 additions & 81 deletions app/components/CountrySelector.tsx
Original file line number Diff line number Diff line change
@@ -1,96 +1,69 @@
import {Link, useLocation, useParams} from '@remix-run/react';
import {Listbox} from '@headlessui/react';
import {IconCaret, IconCheck} from './Icon';
import {useRef} from 'react';
import {getLocalizationFromLang} from '~/lib/utils';
import {Form, useFetchers, useLocation, useParams} from '@remix-run/react';
import {useCountries} from '~/hooks/useCountries';
import {Heading} from '~/components';
import {Heading, Button, IconCheck} from '~/components';
import { useEffect, useRef } from 'react';

export function CountrySelector() {
const countries = useCountries();
const closeRef = useRef<HTMLButtonElement>(null);

if (!countries) return null;

const closeRef = useRef<HTMLDetailsElement>(null);
const {pathname, search} = useLocation();
const {lang} = useParams();
const {language, country} = getLocalizationFromLang(lang);
const languageIsoCode = language.toLowerCase();
const strippedPathname = pathname.replace(new RegExp(`^\/${lang}\/`), '/');
const currentCountry = countries?.find((c) => c.isoCode === country);
const selectedCountry = lang ? countries[`/${lang.toLowerCase()}`] : countries[''];
const strippedPathname = lang ? pathname.replace(`/${lang}`, '') : pathname;

return !countries ? null : (
const fetchers = useFetchers();
useEffect(() => {
closeRef.current?.removeAttribute('open');
}, [fetchers]);

return (
<section className="grid gap-4 w-full md:max-w-[335px] md:ml-auto">
<Heading size="lead" className="cursor-default" as="h3">
Country
</Heading>
<div className="relative">
<Listbox>
{({open}) => {
return (
<>
<Listbox.Button
ref={closeRef}
className={`flex items-center justify-between w-full py-3 px-4 border ${
open
? 'rounded-b md:rounded-t md:rounded-b-none'
: 'rounded'
} border-contrast/30 dark:border-white`}
>
<span>
{currentCountry
? `${currentCountry.name} (${currentCountry.currency.isoCode} ${currentCountry.currency.symbol})`
: '--'}
</span>
<IconCaret direction={open ? 'up' : 'down'} />
</Listbox.Button>

<Listbox.Options
className={`border-t-contrast/30 border-contrast/30 bg-primary dark:bg-contrast absolute bottom-12 z-10 grid
h-48 w-full overflow-y-scroll rounded-t border dark:border-white px-2 py-2
transition-[max-height] duration-150 sm:bottom-auto md:rounded-b md:rounded-t-none
md:border-t-0 md:border-b ${open ? 'max-h-48' : 'max-h-0'}`}
>
{open &&
countries.map((country) => {
const isSelected =
country.isoCode === currentCountry?.isoCode;
const countryIsoCode = country.isoCode.toLowerCase();
return (
<Listbox.Option key={country.isoCode} value={country}>
{({active}) => (
<Link
to={
countryIsoCode !== 'us'
? `/${languageIsoCode}-${countryIsoCode}${strippedPathname}${
search || ''
}`
: `${strippedPathname}${search || ''}`
}
className={`text-contrast dark:text-primary text-contrast dark:text-primary bg-primary
dark:bg-contrast w-full p-2 transition rounded
flex justify-start items-center text-left cursor-pointer ${
active ? 'bg-primary/10' : null
}`}
onClick={() => {
if (!closeRef?.current) return;
closeRef?.current?.click();
}}
>
{country.name} ({country.currency.isoCode}{' '}
{country.currency.symbol})
{isSelected ? (
<span className="ml-2">
<IconCheck />
</span>
) : null}
</Link>
)}
</Listbox.Option>
);
})}
</Listbox.Options>
</>
);
}}
</Listbox>
<details className='border rounded border-contrast/30 dark:border-white open:round-b-none'
ref={closeRef}
>
<summary
className='flex items-center justify-between w-full py-3 px-4'
>
{selectedCountry.label}
</summary>
<div className='overflow-auto border-t py-2 bg-contrast w-full max-h-36'>
{Object.keys(countries).map((countryPath) => {
const locale = countries[countryPath];
const isSelected =
locale.language === selectedCountry.language &&
locale.country === selectedCountry.country;
const hreflang = `${locale.language}-${locale.country}`;
return (
<Form method="post" action="/locale" key={hreflang}>
<input type="hidden" name="language" value={locale.language} />
<input type="hidden" name="country" value={locale.country} />
<input type="hidden" name="path" value={`${strippedPathname}${search}`} />
<Button
className="text-contrast dark:text-primary text-contrast dark:text-primary
bg-primary dark:bg-contrast w-full p-2 transition rounded flex justify-start
items-center text-left cursor-pointer py-2 px-4"
type="submit"
variant="primary"
>
{locale.label}
{isSelected ? (
<span className="ml-2">
<IconCheck />
</span>
) : null}
</Button>
</Form>
);
})}
</div>
</details>
</div>
</section>
);
Expand Down
8 changes: 5 additions & 3 deletions app/components/FeaturedCollections.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {Image} from '@shopify/hydrogen-ui-alpha';
import type {Collection} from '@shopify/hydrogen-ui-alpha/storefront-api-types';
import {Link} from '@remix-run/react';
import {Heading, Section, Grid} from '~/components';
import {Heading, Section, Grid, Link} from '~/components';
import {SerializeFrom} from '@remix-run/server-runtime';

export function FeaturedCollections({
Expand All @@ -26,7 +25,10 @@ export function FeaturedCollections({
return null;
}
return (
<Link key={collection.id} to={`/collections/${collection.handle}`}>
<Link
key={collection.id}
to={`/collections/${collection.handle}`}
>
<div className="grid gap-4">
<div className="card-image bg-primary/5 aspect-[3/2]">
{collection?.image && (
Expand Down
2 changes: 1 addition & 1 deletion app/components/FeaturedSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface FeaturedData {

export function FeaturedSection() {
const featuredProductsFetcher = useFetcher();
const path = usePrefixPathWithLocale('/featured-products');
const path = usePrefixPathWithLocale(`/featured-products`);

useEffect(() => {
featuredProductsFetcher.load(path);
Expand Down
3 changes: 1 addition & 2 deletions app/components/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import clsx from 'clsx';
import {Image, Video} from '@shopify/hydrogen-ui-alpha';
import type {Media, Metafield} from '@shopify/hydrogen/storefront-api-types';
import {Link} from '@remix-run/react';
import {Heading, Text} from '~/components';
import {Heading, Text, Link} from '~/components';

export function Hero({
byline,
Expand Down
4 changes: 3 additions & 1 deletion app/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
type EnhancedMenu,
type EnhancedMenuItem,
useIsHomePath,
usePrefixPathWithLocale,
} from '~/lib/utils';
import {
Drawer,
Expand Down Expand Up @@ -119,14 +120,15 @@ function CartDrawer({isOpen, onClose}: {isOpen: boolean; onClose: () => void}) {
* we persist it between mounting and unmounting.
*/
const topProductsFetcher = useFetcher();
const cartPath = usePrefixPathWithLocale('/cart');

/**
* We load the top products, which are only shown as a fallback when the cart as empty.
* We need to do this here, otherwise we'll incur a network request every time the
* drawer is opened.
*/
useEffect(() => {
isOpen && topProductsFetcher.load('/cart');
isOpen && topProductsFetcher.load(cartPath);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]);

Expand Down
Loading