> ## Documentation Index
> Fetch the complete documentation index at: https://beta.docs.replo.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Languages

> Add languages, choose how visitors are matched, and edit every translation from one table.

export const TryPromptButton = ({prompt, imageSrc, imageAlt = "Template preview", imageStyles = {}, buttonCta = "Build in Replo"}) => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const PUBLISHER_API_BASE_URL = "https://publisher.replo.app";
  const APP_URL = "https://dashboard.replo.app";
  const LoaderIcon = <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
    animation: "spin 1s linear infinite"
  }}>
      <path d="M21 12a9 9 0 1 1-6.219-8.56" />
    </svg>;
  const ChevronIcon = <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="m9 18 6-6-6-6" />
    </svg>;
  async function postJSON(url, body, headers = {}, timeoutMs = 120000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...headers
        },
        body: JSON.stringify(body ?? ({})),
        signal: controller.signal
      });
      const responseText = await response.text();
      if (!response.ok) throw new Error(`API Error: ${response.status}`);
      return responseText ? JSON.parse(responseText) : {};
    } finally {
      clearTimeout(timeoutId);
    }
  }
  async function handleTryPrompt(event) {
    event.preventDefault();
    event.stopPropagation();
    setIsLoading(true);
    setError(null);
    try {
      const body = {
        prompt,
        file: null
      };
      const {seed} = await postJSON(`${PUBLISHER_API_BASE_URL}/api/v1/marketing/issue-marketing-site-jwt`, body);
      if (!seed) throw new Error("No seed returned from API");
      const url = new URL(APP_URL);
      url.searchParams.set("type", "agent");
      url.hash = `seed=${encodeURIComponent(seed)}`;
      window.open(url.toString(), "_blank");
    } catch (caughtError) {
      console.error("Failed to generate prompt:", caughtError);
      setError("Something went wrong. Please try again.");
    } finally {
      setIsLoading(false);
    }
  }
  return <div style={{
    position: "relative",
    display: "inline-block",
    width: "100%"
  }}>
      {}
      {imageSrc && <img src={imageSrc} alt={imageAlt} style={{
    width: "100%",
    height: "auto",
    display: "block",
    borderRadius: "8px",
    opacity: 0.8,
    ...imageStyles
  }} />}

      {}
      <div style={imageSrc ? {
    position: "absolute",
    top: "50%",
    left: "50%",
    transform: "translate(-50%, -50%)",
    zIndex: 10
  } : {
    display: "flex",
    justifyContent: "flex-start",
    margin: "1.25rem 0"
  }}>
        <button type="button" className="try-replo-btn" onClick={handleTryPrompt} disabled={isLoading} style={{
    backgroundColor: "#274AE2",
    color: "#ffffff",
    padding: "0.5rem 1.1rem",
    fontSize: "0.875rem",
    fontWeight: 600,
    fontFamily: "inherit",
    border: "none",
    borderRadius: "9999px",
    cursor: isLoading ? "not-allowed" : "pointer",
    lineHeight: 1.4,
    whiteSpace: "nowrap",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    WebkitFontSmoothing: "antialiased",
    boxSizing: "border-box",
    opacity: isLoading ? 0.7 : 1,
    boxShadow: "0 1px 2px rgba(15, 23, 42, 0.08)",
    transition: "background-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease"
  }}>
          <span style={{
    visibility: isLoading ? "hidden" : "visible",
    display: "inline-flex",
    alignItems: "center",
    gap: "0.375rem"
  }}>
            {buttonCta}
            {ChevronIcon}
          </span>
          {isLoading && <span style={{
    position: "absolute",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center"
  }}>
              {LoaderIcon}
            </span>}
        </button>
      </div>

      {}
      <style>
        {`
          @keyframes spin {
            from {
              transform: rotate(0deg);
            }
            to {
              transform: rotate(360deg);
            }
          }
          .try-replo-btn:hover:not(:disabled) {
            background-color: #1f3ec0 !important;
            box-shadow: 0 4px 12px rgba(39, 74, 226, 0.35);
            transform: translateY(-1px);
          }
          .try-replo-btn:active:not(:disabled) {
            transform: translateY(0);
            box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
          }
          .try-replo-btn:focus-visible {
            outline: 2px solid #274AE2;
            outline-offset: 2px;
          }
        `}
      </style>

      {}
      {error && <div style={{
    marginTop: "12px",
    padding: "12px 16px",
    backgroundColor: "#fee",
    border: "1px solid #fcc",
    borderRadius: "6px",
    color: "#c33",
    fontSize: "14px",
    textAlign: "center"
  }} role="alert">
          {error}
        </div>}
    </div>;
};

Serve one site in multiple languages without duplicating pages. Visitors see the language that fits them, and every string lives in one translations table you can edit from **Site settings**.

Languages live in the [Site Builder](/apps/website-builder): open **More options** (the sliders icon in the top bar), choose **Site settings**, and select the **Languages** tab. You can also ask Replo in chat.

```text theme={null}
Add French Canadian (fr-CA) as a second language, detect visitors by browser language, and give each language its own address.
```

<TryPromptButton prompt="Add French Canadian (fr-CA) as a second language, detect visitors by browser language, and give each language its own address." />

## How it works

You keep a single set of pages. Replo stores the text for each language separately, then picks a language for each visitor:

1. **An address they already opened.** If they land on a language-specific URL, that language wins and Replo remembers the choice.
2. **Detection.** Otherwise Replo matches the visitor using the rule you picked (browser language or country).
3. **Your default language.** If nothing else matches, they get the language you marked as default.

A close match never shows an error page. A French visitor still gets French if you only offer `fr-FR` and their browser asked for `fr-CA`.

## Turn on languages

You need at least 2 languages to start. Setup rewrites the site so every page can serve those languages, and it can take a few minutes.

<Steps>
  <Step title="Open the Languages tab">
    In the Site Builder, open **More options**, choose **Site settings**, and
    select **Languages**.
  </Step>

  <Step title="Add languages">
    Use a standard language tag: a language code, optionally with a region, like
    `en-CA`, `fr`, or `zh-Hant`. Mark one as **Default**. You can add more
    languages later from the same tab.
  </Step>

  <Step title="Choose how visitors are matched">
    **Browser language** uses each visitor's preferred language. **Country**
    uses where they are visiting from, and their preferred language breaks ties
    when that country has more than one of your languages.
  </Step>

  <Step title="Choose web addresses">
    **Localized** gives each language its own address (`/fr-CA/about`) that
    search engines can index separately. This is the recommended option.
    **Single** keeps one address and swaps the content by visitor language.
  </Step>

  <Step title="Set up languages">
    Click **Set up languages**. Replo adds the languages to your site and
    translates existing copy into each one. Stay on the page until it finishes.
  </Step>

  <Step title="Publish">
    Preview each language in the Site Builder, then [publish](/publishing) so
    visitors see the new languages on your live site.
  </Step>
</Steps>

## Edit translations

After setup, the Languages tab includes an **Edit translations** button. That opens a table of every translation key across your languages.

* Rows are keys (the pieces of copy on your pages). Columns are languages, with the default first.
* Edit a cell and click **Save translations**.
* If a language is missing keys, **Fill in missing translations** writes the gaps for you. Review them before you publish.
* Page copy you add later lands in this table as it is translated. You do not fork a page per language.

## Preview a language

On a localized site, the Site Builder shows a language picker in the preview toolbar. Pick a language to see that version of the page you have open, the same way a visitor would after choosing it.

## Right-to-left languages

Arabic, Hebrew, Persian, and Urdu read right to left, so the page has to mirror: navigation moves to the right, text right-aligns, and arrows point the other way. Translating the words alone leaves the layout backwards.

Ask Replo to handle it and it will update the site for you — set the reading direction from the language, flip the layout and directional icons, and load a font that covers the script.

```text theme={null}
Add Arabic (ar) as a second language and make sure the site lays out right-to-left for it.
```

<TryPromptButton prompt="Add Arabic (ar) as a second language and make sure the site lays out right-to-left for it." />

Replo works this out from the language itself, so you do not need to say which languages are right-to-left. Once it is done, preview the language and check that the page reads right to left: navigation starts on the right, text is right-aligned, and forward arrows point left. If a section still looks backwards, tell Replo which one and it will fix that part.

Adding a right-to-left language later works the same way — ask, and Replo mirrors the existing pages rather than rebuilding them.

## Languages on their own domains

Use **Domains** on the Languages tab when a hostname should serve a specific set of languages, for example `shop.example.ca` for `en-CA` and `fr-CA`, and `example.fr` for `fr-FR`.

* Enter a bare hostname (`shop.example.com`), with no `https://`, path, or port.
* Pick which of your site's languages that domain serves, and optionally a default for that domain.
* A domain that serves one language uses clean addresses with no language prefix.
* Connect the hostname on the [Domains](/custom-domains) tab as well. Mapping it here does not replace connecting the domain.

Unknown hosts, including preview URLs, use your site-wide languages.

## FAQ

<AccordionGroup>
  <Accordion title="Do I duplicate pages for each language?">
    No. You keep one page. The translations table holds the copy for every
    language, so a headline change is one key, not a second page to keep in
    sync.
  </Accordion>

  <Accordion title="What counts as a language tag?">
    A language code, optionally with a region: `fr`, `fr-CA`, `zh-Hant`. Replo
    canonicalizes casing (`fr-ca` becomes `fr-CA`). Names like "French" are not
    tags.
  </Accordion>

  <Accordion title="Can I start with one language?">
    Not from this tab. Setup needs at least 2 languages. Add your default plus
    the first translation, then you can add more later. You cannot remove the
    default, or drop below 2 languages, once the site is set up.
  </Accordion>

  <Accordion title="Should I pick Localized or Single addresses?">
    Pick **Localized** unless you have a reason not to. Separate addresses let
    search engines index each language. **Single** is for when you want one URL
    and are fine swapping the content for each visitor.
  </Accordion>

  <Accordion title="Do I need to republish after changing languages?">
    Yes. Adding languages, changing detection or addresses, mapping domains, and
    saving translations all apply to your live site on the next publish. Preview
    first.
  </Accordion>

  <Accordion title="The tab says Managed in code. Can I still change languages?">
    The Languages tab can edit the standard setup. If language routing was
    written in a way the tab cannot safely change, it shows **Managed in code**.
    Ask Replo in chat to change it, rather than fighting the tab.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Site Builder" href="/apps/website-builder">
    Preview each language before you publish.
  </Card>

  <Card title="Custom domains" href="/custom-domains">
    Connect the hostnames you map to languages.
  </Card>

  <Card title="Page and SEO settings" href="/features/site-page-settings">
    Control how search engines see your site.
  </Card>

  <Card title="Publishing" href="/publishing">
    Publish so language changes go live.
  </Card>
</CardGroup>
