Multilingual AI visibility: monitor answers by language
A program that monitors only English prompts measures only English-language answers. The engines reply in the language they are asked in, can cite sources from that language’s web, and can name different brands. The French answer to “iPhone 17 ou Galaxy S26 ?” need not be a translation of the English answer, and the way to find out how far apart they are for your category is to collect both.
ChatGPT, Gemini, AI Mode and Google Search answer from 213 countries, Copilot, Perplexity and Grok take any country, and Google News the 71 countries with a Google News edition, with hl for the language. The coverage check in the setup below skips pairs an engine does not serve.
Two axes: language and market
- Language is the language of the question. For the AI assistants it is set by the text of
promptitself; there is no separate language parameter. - Market is where the question is asked from. Every engine takes
country, an ISO 3166-1 alpha-2 code. Gemini also takes a US state; the other assistants answer at country level and refusestatewith a 400.
Google Search, AI Mode and Google News expose both axes as parameters.
| Engine | Market | Language | Finer location |
|---|---|---|---|
| Gemini | country (213 countries) |
Prompt text | state (US only) |
| ChatGPT | country (213 countries) |
Prompt text | none |
| Copilot, Perplexity, Grok | country (any) |
Prompt text | none |
AI Mode (/v1/monitor/aimode) |
country or gl |
Prompt text, plus hl |
location or uule |
Google Search (/v1/monitor/google) |
country or gl |
Query text, plus hl |
location or uule |
Google News (/v1/monitor/google/news) |
country or gl |
Query text, plus hl |
none |
hl is Google’s interface language code (for example en, de, pt-BR). It is free-form and overrides the language the API would otherwise derive from the country, so the interface language and the result geography can differ. gl is an alternative to country using Google’s own parameter name; send one or the other, because sending both with different values returns 400.
Why answers diverge across languages
An answer that cites the web is assembled from searches the engine runs. When the question is in French and asked from France, those searches are likely to be in French and to surface French-language pages, so the pool of citable sources changes, and with it the brands the answer names. The searches themselves are not visible: ChatGPT’s searchQueries[] field comes back empty on the ChatGPT answers served today. Their effect is: compare the language and domains of the cited sources[] across markets.
Your English-language pages, however strong, may not be in the pool for a German answer, so visibility has to be measured per language.
Decide which market-language pairs to monitor
A market is not always one language. Monitor pairs, not countries:
| Situation | Pairs to monitor |
|---|---|
| One dominant language | FR + French prompts |
| Several official languages | CH + German, CH + French; CA + English, CA + French; BE + Dutch, BE + French |
| Large second-language population you sell to | US + Spanish prompts alongside US + English |
| English used for business buying | NL or SE + English prompts, alongside the local language |
| No revenue or sales plans | Skip; add later as a new cohort |
Prioritize by revenue or pipeline in each pair, not by country size.
Setting it up
1. Check engine coverage for each market
Not every engine accepts every country. GET /v1/countries?model=<engine> returns the supported ISO codes for that engine (chatgpt, gemini, or google for Google News). Build your matrix from the intersection:
import os
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['ANSWERLINE_API_KEY']}"}
ENGINES = ["chatgpt", "gemini"]
MARKETS = ["US", "FR", "DE", "CH", "JP"]
supported = {}
for engine in ENGINES:
r = requests.get(f"{API}/v1/countries", headers=HEADERS, params={"model": engine}, timeout=30)
r.raise_for_status()
supported[engine] = set(r.json()) # a JSON array of country codes
for market in MARKETS:
missing = [e for e in ENGINES if market not in supported[e]]
print(market, "all engines" if not missing else f"not supported on: {missing}")
2. Write native prompts per intent
Start from your English prompt set as a list of intents, each with a stable ID. For each market-language pair, have a native speaker write the prompt the way a local buyer would ask it:
- Local currency and units (“unter 100 Euro”, not “under $100”).
- Local competitors and retailers, which may differ from your English competitor set.
- Local phrasing. A German shopper and a French shopper do not ask the same question in the same shape.
Keep the intent ID on every prompt so you can compare “the same question” across markets even though the wording differs. Prompt set design covers intent grouping.
3. Define brand aliases per language
Brand detection fails silently in other scripts and languages. Keep a per-language alias list: transliterations (Japanese katakana forms of Latin brand names), local product names, and legal-entity names that appear in local press. Measuring brand mentions covers matching with word boundaries.
4. Submit one batch per run
Each task carries the market in country and the language in the prompt text. Idempotency keys include the intent ID, market, language, engine and date, so a retried submission never creates duplicates.
import datetime
RUN = datetime.date.today().isoformat()
prompts = [
{"intent": "phone-compare", "country": "FR", "lang": "fr", "prompt": "iPhone 17 ou Galaxy S26 ?"},
{"intent": "phone-compare", "country": "DE", "lang": "de", "prompt": "iPhone 17 oder Galaxy S26?"},
{"intent": "phone-compare", "country": "CH", "lang": "fr", "prompt": "iPhone 17 ou Galaxy S26 ?"},
{"intent": "phone-compare", "country": "US", "lang": "en", "prompt": "iPhone 17 or Galaxy S26?"},
]
tasks, meta = [], []
for p in prompts:
jobs = [
("CHATGPT", {"prompt": p["prompt"], "country": p["country"]}),
("GEMINI", {"prompt": p["prompt"], "country": p["country"]}),
]
for task_type, payload in jobs:
if task_type.lower() not in supported or p["country"] not in supported[task_type.lower()]:
continue
tasks.append({
"taskType": task_type,
"payload": payload,
"idempotencyKey": f"ml-{p['intent']}-{p['country']}-{p['lang']}-{task_type}-{RUN}",
"webhook": {"url": "https://your-app.example/hooks/answers"},
})
meta.append({**p, "engine": task_type})
resp = requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=tasks, timeout=360)
resp.raise_for_status()
body = resp.json()
print(body["summary"])
for item in body["results"]:
if not item["success"]:
print(meta[item["index"]], item["error"]["code"])
A batch takes up to 500 tasks; split larger runs. Results arrive at the webhook as the same JSON that GET /v1/async/task/{taskId} returns, with the answer under response.result. Verify the signature before trusting the body; see webhooks and verifying webhook signatures.
The same submission with the TypeScript SDK (@answerline/sdk):
import { Client } from "@answerline/sdk";
const client = new Client({ apiKey: process.env.ANSWERLINE_API_KEY! });
const run = new Date().toISOString().slice(0, 10);
const markets = [
{ intent: "phone-compare", country: "FR", lang: "fr", prompt: "iPhone 17 ou Galaxy S26 ?" },
{ intent: "phone-compare", country: "DE", lang: "de", prompt: "iPhone 17 oder Galaxy S26?" },
];
const chatgptCountries = new Set(await client.countries("chatgpt"));
const tasks = markets
.filter((m) => chatgptCountries.has(m.country))
.map((m) => ({
taskType: "CHATGPT" as const,
payload: { prompt: m.prompt, country: m.country },
idempotencyKey: `ml-${m.intent}-${m.country}-${m.lang}-CHATGPT-${run}`,
}));
const batch = await client.createBatch(tasks);
for (const item of batch.results) {
if (!item.success) console.warn(tasks[item.index].idempotencyKey, item.error.code);
}
5. Analyze per market-language pair
For each stored result, compute the same metrics you use in English, grouped by market, language and engine:
from collections import Counter, defaultdict
from urllib.parse import urlparse
ALIASES = {"en": ["Acme"], "fr": ["Acme"], "de": ["Acme"], "ja": ["Acme", "アクメ"]}
MY_DOMAINS = {"acme.com", "acme.fr", "acme.de"}
def host(url):
h = urlparse(url).hostname or ""
return h[4:] if h.startswith("www.") else h
stats = defaultdict(lambda: {"answers": 0, "mentioned": 0, "mine": 0, "sources": 0, "domains": Counter()})
def record(meta, result):
s = stats[(meta["country"], meta["lang"], meta["engine"])]
text = result.get("text", "").lower()
s["answers"] += 1
s["mentioned"] += any(a.lower() in text for a in ALIASES[meta["lang"]])
for src in result.get("sources", []):
d = host(src["url"])
s["sources"] += 1
s["mine"] += d in MY_DOMAINS
s["domains"][d] += 1
def report():
for (country, lang, engine), s in sorted(stats.items()):
rate = s["mentioned"] / s["answers"]
share = s["mine"] / s["sources"] if s["sources"] else 0.0
top = ", ".join(d for d, _ in s["domains"].most_common(5))
print(f"{country}-{lang} {engine}: mention rate {rate:.0%}, citation share {share:.0%}, top sources: {top}")
Call record(meta, response["result"]) from your webhook handler for each COMPLETED task, then report() after the run. MY_DOMAINS lists every localized domain you own; a French answer citing acme.fr is a citation for you.
Reading the results
- Mentioned in English, absent in German: a content and coverage problem in that language, not a brand awareness problem. Look at which German sources are cited instead.
- Cited on your English domain in a French answer: the engine found no suitable French page of yours, or ranked the English one higher. Publishing a localized page tests which.
- Local sources dominate: the top domains per market are your target list for that market: local review sites, local press, local marketplaces and forums. How AI engines choose citations covers the selection path; Reddit citations covers community sources.
- Two languages in one country differ:
CH-German andCH-French answers can cite different pools. Treat them as separate markets in planning. - Sources in an unexpected language: if a Japanese answer cites partly English pages, English-language pages are still in the pool for that market.
Localized pages and hreflang
For Google’s surfaces, Google documents hreflang (as HTML tags, HTTP headers or sitemap entries) as the way to tell it about localized variants of the same content, with ISO 639-1 language codes and optional ISO 3166-1 alpha-2 region codes, and x-default as the fallback. It also says it does not use hreflang or the HTML lang attribute to detect a page’s language; it detects language algorithmically (Google Search Central: localized versions, checked 2026-09-17). That page does not discuss AI answers, so treat correct hreflang as hygiene for Google’s index and measure citations of your localized URLs rather than assuming an effect.
What it costs
The price depends on the engine and options, never on the language. As async tasks at the Starter tier:
| Task | Price |
|---|---|
CHATGPT |
$0.0006 |
GEMINI |
$0.0006 |
| Per prompt per market-language pair per run | $0.0012 |
For example, 30 intents across 6 market-language pairs is 180 prompts, or $0.216 per run; weekly for four weeks is $0.864. See pricing and cost planning.
Pitfalls
- Word-for-word translation. It produces prompts no local buyer types. Write natively, keep the intent ID.
- Accidental language and market mismatch. A French prompt sent with
country: "US"is a valid test of French speakers in the US, but label it that way, not as France. - Sending
countryandglwith different values to Google endpoints. That is a400; send one. - Comparing absolute rates across markets. Prompt wording differs by language, so compare trends within a market and use cross-market gaps as leads, not verdicts.
- English-only brand matching. Missing transliterations undercount mentions in non-Latin scripts.
- Assuming
stateworks outside the US.stateis US-only; for city-level targeting on Google Search and AI Mode, uselocation.
For state-level differences inside the US, see local AI answers by state. To start, follow the quickstart or see the ChatGPT engine page and Gemini engine page.
Questions
Do AI assistants give different answers in different languages?
They can. The answer is written in the prompt's language and may cite different sources and name different brands. The only reliable way to know for your category is to run native-language prompts per market and compare the stored answers.
How do I set the language and market for a monitored AI answer?
Every engine takes a country code in country. For ChatGPT, Gemini, Copilot, Perplexity and Grok, the language is the language you write the prompt in; ChatGPT and Gemini answer from 213 countries, and Copilot, Perplexity and Grok take any country. Google News takes the 71 countries with a Google News edition and also accepts hl for the interface language and gl as an alternative to country, as does AI Mode. Google Search takes country or gl, and hl, which defaults to the country's language.
Which countries does each engine support?
Call GET /v1/countries with a model filter such as chatgpt or gemini, or google for Google News. It returns the ISO 3166-1 alpha-2 codes that engine accepts. Copilot, Perplexity and Grok take any country, and AI Mode and Google Search answer from 213.
Should I translate my English prompt set?
Use the English set as an intent list, then have a native speaker write each prompt as a local buyer would ask it, with local currency, units, competitors and idioms. Word-for-word translations produce prompts real users do not type.
Does monitoring in another language cost more?
No. The price depends on the engine and options, not the language: for example $0.0006 for a ChatGPT task and $0.0006 for Gemini at the Starter tier.
Does hreflang make AI engines cite my localized pages?
No source says so. Google documents hreflang as the way to tell it about localized variants of a page, and says it detects page language algorithmically rather than from hreflang. Measure which localized URLs get cited instead of assuming.