JavaRush /Курсы /ChatGPT Apps /Откуда брать locale: openai/locale, _meta["openai/userLoc...

Откуда брать locale: openai/locale, _meta["openai/userLocation"]

ChatGPT Apps
9 уровень , 1 лекция
Открыта

1. Почему вообще важно знать locale из платформы, а не спрашивать у пользователя каждый раз

Если подойти к теме локализации «по‑старинке», логика обычно такая: показать модальное окно «Выберите язык» и хранить результат в localStorage. В ChatGPT Apps подход другой: у нас уже есть умная платформа, и она щедро подсовывает сигналы о языке и регионе. Нужно научиться их использовать и не мучить пользователя лишними вопросами.

ChatGPT в каждом запросе к вашему App добавляет в контекст:

  • предпочтительную локаль пользователя (язык + регион) — в поле openai/locale / _meta["openai/locale"];
  • геолокацию/регион пользователя — в поле _meta["openai/userLocation"].

На стороне виджета (фронтенд) вы получаете locale через window.openai или хук SDK; на стороне MCP/бэкенда — через _meta в запросе MCP.

В результате нормальный сценарий выглядит так: пользователь пишет «Подбери подарок маме в пределах 50 евро». ChatGPT уже знает его locale и userLocation, платформа передаёт эти сигналы вашему App, и вы:

  • показываете UI на понятном языке,
  • подгружаете правильный язык каталога,
  • форматируете цены в нужной валюте и формате.

Без отдельного диалога «Кстати, а какой у вас язык?».

2. Сигнал №1: openai/locale — язык и регион пользователя

Что это за поле и как оно выглядит

openai/locale — это строка в формате BCP‑47, который вы наверняка видели: "en", "en-US", "bg", "bg-BG", "it", "it-IT" и т.д.

Важно, что платформа:

  • может прислать просто язык ("en", "ru"),
  • может прислать язык + регион ("en-US", "en-GB", "fr-CA").

BCP‑47 — стандарт, с которым отлично работают и Intl-API в браузере, и большинство i18n‑библиотек. То есть openai/locale можно почти напрямую прокидывать в Intl.NumberFormat, в движок переводов и внутрь ваших tools.

Где locale доступен в виджете

В кастомном UI, который рендерится внутри ChatGPT, Apps SDK предоставляет глобальный объект window.openai, где есть locale.

Типично это выглядит так (TypeScript, Next.js 16, наш виджет GiftGenius):

// src/app/widgets/gift-widget.tsx
declare global {
  interface Window {
    openai?: { locale?: string };
  }
}

function getOpenAiLocale(): string {
  if (typeof window === "undefined") return "en";
  return window.openai?.locale || "en";
}

В реальном приложении проще сделать хук, который будет работать и в песочнице ChatGPT, и в Storybook:

// src/app/hooks/useOpenAiLocale.ts
import { useEffect, useState } from "react";

export function useOpenAiLocale(defaultLocale: string = "en") {
  const [locale, setLocale] = useState(defaultLocale);

  useEffect(() => {
    if (typeof window === "undefined") return;
    const next = window.openai?.locale || defaultLocale;
    setLocale(next);
  }, [defaultLocale]);

  return locale;
}

Теперь в любом компоненте:

import { useOpenAiLocale } from "../hooks/useOpenAiLocale";

export function GiftHeader() {
  const locale = useOpenAiLocale();

  return (
    <h2>
      {/* позже здесь будет t('titles.gift_search') */}
      {locale.startsWith("ru") ? "Подбор подарка" : "Gift search"}
    </h2>
  );
}

На лекции 4 мы аккуратно вынесем все строки в словари, но уже сейчас мы привязали UI к реальному сигналу от платформы, а не к рандомному navigator.language. Этот хук узкоспециализированный; в реальном проекте его удобно построить поверх более общего механизма доступа к глобалам ChatGPT — к нему мы вернёмся в отдельном разделе ниже.

Где locale доступен в MCP/бэкенде

Когда ChatGPT вызывает MCP‑инструмент, SDK передаёт _meta["openai/locale"] в JSON‑rpc‑запросе. На TypeScript‑сервере (наш GiftGenius MCP) это обычно доступно во втором аргументе обработчика инструмента.

Пример:

// src/mcp/server.ts
import { McpServer } from "@openai/mcp-sdk";

const server = new McpServer();

server.registerTool(
  "suggest_gifts",
  {
    title: "Подбор подарков",
    description: "Предлагает список подарков по предпочтениям",
    inputSchema: {
      type: "object",
      properties: {
        recipient: { type: "string" },
        budget: { type: "number" }
      },
      required: ["recipient", "budget"]
    }
  },
  async ({ input }, extra) => {
    const locale = extra?._meta?.["openai/locale"] || "en";
    // дальше можно грузить правильный каталог
    const gifts = await loadGiftCatalog(locale);
    // ...
    return {
      content: [
        {
          type: "text",
          text: `Found ${gifts.length} gifts for locale ${locale}`
        }
      ],
      structuredContent: { gifts }
    };
  }
);

Таким образом locale живёт сквозь весь стек: ChatGPT → Apps SDK → ваш MCP‑сервер.

Insight

У каждого mcp-tool на сервере есть параметр extra, куда mcp-сервер кладет все данные, которые не поместились в inputSchema. Вот пример такого объекта:

{
  sessionId: undefined,			//всегда undefined, используйте `openai/subject` ниже
  _meta: {
    'openai/userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
    'openai/locale': 'en-US',		//locale компьютера пользователя, может не совпадать с языком в чате
    'openai/userLocation': {		//достаточно точное местоположение пользователя
      city: 'London',
      region: 'London City',
      country: 'GB',
      timezone: 'Europe/London',
      latitude: '5.45466',
      longitude: '-0.52380'
    },
    timezone_offset_minutes: -240,	// смещение часового пояся
    'openai/subject': 'v1/sEtRuS92UEOPNdwzEUZORfeOKf7XSk2KZoIUGfAsb68BzZ8h5FAOgrH'	//это sessionId
  },
  authInfo: undefined,
  requestId: 1,
  requestInfo: {
    headers: {
      accept: 'application/json, text/event-stream',
      'accept-encoding': 'gzip, deflate, br, zstd',
      'access-control-allow-headers': '*',
      'access-control-allow-methods': 'GET,POST,PUT,DELETE,OPTIONS',
      'access-control-allow-origin': '*',
      'content-length': '542',
      'content-type': 'application/json',
      host: 'test.ngrok.app',					//родной домен приложения
      'mcp-protocol-version': '2025-11-25',	
      traceparent: '00-69399d3a000000004fb8cc13dc3a2203-8748a8698107eb34-00',
      tracestate: 'dd=s:-1;p:01514e334c1ccef5;t.dm:-3',
      'user-agent': 'openai-mcp/1.0.0',
      'x-datadog-parent-id': '6089244476286233754',
      'x-datadog-sampling-priority': '-1',
      'x-datadog-tags': '_dd.p.tid=69399c3a00000000,_dd.p.dm=-3',
      'x-datadog-trace-id': '5744565710382309891',
      'x-forwarded-for': '199.210.139.232',
      'x-forwarded-host': 'test.ngrok.app',
      'x-forwarded-port': '3001',
      'x-forwarded-proto': 'https'
    }
  },
}

Возможно часть заголовкой тут заполнял ngrock, но интересных данных все равно много.

3. Сигнал №2: _meta["openai/userLocation"] — география пользователя

Структура и смысл

_meta["openai/userLocation"] — это объект с гео‑информацией: страна, регион, город, часовой пояс и даже координаты. Примерно так:

{
  "city": "London",
  "region": "England",
  "country": "GB",
  "timezone": "Europe/London",
  "latitude": 51.5074,
  "longitude": -0.1278
}

Главные поля, которыми вы будете реально пользоваться в GiftGenius:

  • country — двухбуквенный ISO‑код страны, критичен для ассортимента и валюты;
  • timezone — пригодится для форматов дат/времени и напоминаний.

Insight

Экспериментально проверено — определение userLocation работает очень качественно. Данные приходят в каждый вызов MCP-tool через параметр extra._meta["openai/userLocation"]. Можете рассчитывать на них при разработке своих приложений.

Как использовать userLocation в MCP‑инструментах

На MCP‑сервере userLocation живёт в _meta["openai/userLocation"] рядом с _meta["openai/locale"].

Расширим пример нашего инструмента:

server.registerTool(
  "suggest_gifts",
  { /* schema как выше */ },
  async ({ input }, extra) => {
    const meta = extra?._meta ?? {};
    const locale = (meta["openai/locale"] as string) || "en";
    const userLocation = meta["openai/userLocation"] as
      | { country?: string; city?: string }
      | undefined;

    const country = userLocation?.country || "US";

    const gifts = await loadGiftCatalog(locale, country);

    return {
      content: [
        {
          type: "text",
          text: `Found ${gifts.length} gifts for locale=${locale}, country=${country}`
        }
      ],
      structuredContent: { gifts }
    };
  }
);

Функция loadGiftCatalog(locale, country) уже может:

  • выбрать нужный JSON‑файл: gift_catalog.en-US.json, gift_catalog.ru-RU.json,
  • отфильтровать товары, которые нельзя доставить в эту страну,
  • выбрать базовую валюту.

Чуть позже в commerce‑модулях вы будете на базе country выбирать налоговые правила и маппить на правильные SKU, но с архитектурной точки зрения вы всё равно опираетесь на один и тот же сигнал — country.

Как userLocation дополняет locale

Классический пример:

locale = "en", userLocation.country = "DE".

Логика может быть такой:

  • UI и подсказки — на английском (respect locale);
  • формат валюты и цены — евро, потому что пользователь физически в Германии;
  • список подарков — только те, что доставляются в DE.

В GiftGenius это можно выразить небольшой хелпер‑функцией:

export function deriveCurrency(locale: string, country?: string): string {
  if (country === "DE") return "EUR";
  if (country === "JP") return "JPY";   
  if (locale === "zh_CN") return "CNY"; 
  return "USD";
}

И использовать на бэкенде / фронтенде для форматирования цен:

const currency = deriveCurrency(locale, country);
const formatted = new Intl.NumberFormat(locale, {
  style: "currency",
  currency
}).format(price);

На бэкенде мы уже научились использовать locale и country для выбора каталога и валюты. Дальше важно аккуратно донести те же сигналы до UI в виджете, чтобы пользователь видел тексты и цены в ожидаемом формате.

4. Как получать locale и userLocation в виджете GiftGenius

Мы уже посмотрели, как locale и userLocation живут на стороне MCP и влияют на каталоги и валюту. Теперь разберёмся, как аккуратно забрать locale в виджет GiftGenius и использовать его прямо в React‑UI.

Важно: в виджете у нас есть прямой доступ только к locale (через window.openai и хуки SDK). userLocation живёт в _meta и используется на стороне MCP/бэкенда — с ним мы уже работали выше.

В Apps SDK помимо «сырого» window.openai есть утилиты в виде React‑хуков. В документации описываются хуки вроде useOpenAiGlobal("locale"), которые вытаскивают значения глобального контекста ChatGPT в React‑компоненты.

Смоделируем такой хук сами, чтобы было понятно, что под капотом происходит.

Базовый хук useOpenAiGlobal

Раньше мы сделали узкоспециализированный useOpenAiLocale. На практике удобнее иметь один универсальный хук для доступа к глобалам ChatGPT — на его основе легко собрать и useOpenAiLocale, и другие обёртки. Представим такой хук:

// src/app/hooks/useOpenAiGlobal.ts
import { useEffect, useState } from "react";

type OpenAiGlobals = {
  locale?: string;
  // сюда позже можно добавить theme, userAgent и т.п.
};

export function useOpenAiGlobal<K extends keyof OpenAiGlobals>(
  key: K,
  fallback?: NonNullable<OpenAiGlobals[K]>
): NonNullable<OpenAiGlobals[K]> {
  const [value, setValue] = useState<NonNullable<OpenAiGlobals[K]>>(
    (fallback ?? "") as NonNullable<OpenAiGlobals[K]>
  );

  useEffect(() => {
    if (typeof window === "undefined") return;
    const globals = (window.openai || {}) as OpenAiGlobals;
    const next = globals[key] ?? fallback;
    if (next !== undefined) {
      setValue(next as NonNullable<OpenAiGlobals[K]>);
    }
  }, [key, fallback]);

  return value;
}

Теперь useOpenAiGlobal("locale", "en") даёт нам актуальное значение locale с дефолтным значением "en".

Применение в виджете GiftGenius

Сделаем небольшой компонент, который показывает локализованное приветствие и текущую локаль для отладки:

// src/app/widgets/GiftWelcome.tsx
"use client";

import React from "react";
import { useOpenAiGlobal } from "../hooks/useOpenAiGlobal";

export function GiftWelcome() {
  const locale = useOpenAiGlobal("locale", "en");

  const greeting =
    locale.startsWith("ru") || locale.startsWith("uk")
      ? "Привет! Я помогу подобрать подарок."
      : "Hi! I’ll help you find a great gift.";

  return (
    <div>
      <p>{greeting}</p>
      <small style={{ opacity: 0.6 }}>Debug locale: {locale}</small>
    </div>
  );
}

Пока никаких словарей и i18n‑библиотек — это будет позже. Сейчас важно, что мы уже умеем честно взять язык из ChatGPT, а не из случайных предположений.

5. Когда нужно спрашивать пользователя про язык явно

Если openai/locale и userLocation такие крутые, можно ли вообще никогда не спрашивать пользователя, на каком языке он хочет работать? К сожалению, иногда приходится.

Когда сигналов недостаточно

Есть несколько типичных ситуаций:

  • Аккаунт ChatGPT англоязычный (locale = "en"), но пользователь пишет по‑русски. Модель отвечает по‑русски, но UI вы даёте на английском.
  • Пользователь в Германии (userLocation.country = "DE"), locale = "en", а вы готовы дать и немецкий, и английский интерфейс.
  • Приложение критично к языку коммуникации: психотерапия, юридические консультации, обучение. Там точность понимания важнее комфорта автодетекта.

В таких случаях уместно задать короткий и вежливый вопрос один раз в начале сценария, а дальше помнить выбор.

Как задать вопрос о языке ненавязчиво

Обычно формулировку делают максимально простой и визуальной, например:

  • «На каком языке вам удобнее: English или Русский?»
  • «Мы определили ваш язык как English. Хотите переключиться на другой?»

В ChatGPT App это можно сделать двумя способами:

  • Через UI виджета: отрисовать небольшой переключатель языков вверху.
  • Через follow‑up сообщение в чат от имени App: отправить текстовый follow‑up с вопросом, а затем обработать ответ.

Код: простой выбор языка в GiftGenius

Сделаем компонент‑переключатель, который:

  • берёт стартовый язык из locale,
  • даёт пользователю выбрать ru или en,
  • хранит выбор в состоянии виджета (пока просто в React‑state).
// src/app/widgets/LanguageSwitcher.tsx
"use client";

import React, { useState, useEffect } from "react";
import { useOpenAiGlobal } from "../hooks/useOpenAiGlobal";

type SupportedLocale = "en" | "ru";

export function LanguageSwitcher(props: {
  onChange?: (locale: SupportedLocale) => void;
}) {
  const initialLocale = useOpenAiGlobal("locale", "en");
  const [locale, setLocale] = useState<SupportedLocale>("en");

  useEffect(() => {
    const normalized: SupportedLocale = initialLocale.startsWith("ru")
      ? "ru"
      : "en";
    setLocale(normalized);
    props.onChange?.(normalized);
  }, [initialLocale, props]);

  const handleChange = (next: SupportedLocale) => {
    setLocale(next);
    props.onChange?.(next);
  };

  return (
    <div style={{ marginBottom: 8 }}>
      <span style={{ marginRight: 8 }}>
        {locale === "ru" ? "Язык:" : "Language:"}
      </span>
      <button
        type="button"
        onClick={() => handleChange("en")}
        style={{ fontWeight: locale === "en" ? "bold" : "normal" }}
      >
        EN
      </button>
      <button
        type="button"
        onClick={() => handleChange("ru")}
        style={{ fontWeight: locale === "ru" ? "bold" : "normal", marginLeft: 4 }}
      >
        RU
      </button>
    </div>
  );
}

А в основном виджете GiftGenius можно уже выбирать тексты/словарь по selectedLocale, а не по «сырым» данным от ChatGPT.

В будущих лекциях вы замените локальный state на более устойчивое хранение (например, пробросите выбранный язык в MCP / Gateway по _meta["openai/subject"]), но паттерн останется тем же.

6. Как передавать locale и userLocation в backend и хранить их

Сигналы от ChatGPT приходят «сверху», но жизнь на этом не заканчивается. Дальше эти данные надо донести до ваших инструментов и сервисов, не потерять по дороге и не заставлять модель угадывать язык заново.

Явное поле locale в аргументах tools

Самый надёжный приём — добавить locale (и при желании country) как отдельные поля в inputSchema инструмента. Тогда модель получает явный сигнал: «надо заполнить вот это поле».

server.registerTool(
  "suggest_gifts",
  {
    title: "Gift suggestions",
    description: "Suggest gifts based on recipient and budget",
    inputSchema: {
      type: "object",
      properties: {
        recipient: { type: "string" },
        budget: { type: "number" },
        locale: {
          type: "string",
          description: "Current user UI locale, BCP-47 (e.g. en-US, fr-FR)"
        },
        country: {
          type: "string",
          description: "ISO country code (e.g. US, DE)"
        }
      },
      required: ["recipient", "budget"]
    }
  },
  async ({ input }, extra) => {
    // Если модель не заполнила locale/country, подстрахуемся из _meta:
    const meta = extra?._meta ?? {};
    const locale = input.locale || (meta["openai/locale"] as string) || "en";
    const country =
      input.country ||
      (meta["openai/userLocation"] as any)?.country ||
      "US";

    // ...
  }
);

Это уменьшает «магии» внутри сервера: он чётко видит аргументы, которые модель собралась использовать.

Хранение locale на уровне сессии / пользователя

В архитектуре с MCP Gateway (будущие модули) принято хранить «состояние клиента»: locale, currency, предпочтения. Сейчас нам важно только осознать идею: один раз прочли сигналы от ChatGPT — дальше используем их как часть сессионного состояния, а не пересчитываем каждый раз.

Условный псевдокод:

// gateway.ts
const sessionState = new Map<string, { locale: string; country?: string }>();

function onMcpRequest(request: any) {
  const subject = request._meta?.["openai/subject"]; // анонимный user id
  const locale = request._meta?.["openai/locale"] || "en";
  const country = request._meta?.["openai/userLocation"]?.country;

  if (subject) {
    sessionState.set(subject, { locale, country });
  }

  // далее передаём locale/country в конкретный MCP-сервер
}

В рамках этой лекции вам не нужно реализовывать Gateway, достаточно понимать, что locale и userLocation — кандидаты в такое «сессионное состояние».

Insight

Экспериментальные данные: request._meta?.["openai/locale"] показывает текущую установленную locale пользователя. Язык общения можно получить в качестве параметра tool через inputSchema.

Я выставил у себя на компьютере EN-локаль, а общался с ChatGPT на немецком (DE). В результате:

  • request._meta?.["openai/locale"] было равно EN
  • locale, полученный как параметр tool через inputSchema, был равен DE

7. Locale vs автоопределение языка по тексту

Иногда разработчиков тянет к идее: «Давайте просто будем детектить язык по тексту пользователя, LLM же всё умеет». На практике это почти всегда хуже, чем опираться на openai/locale.

Причины довольно приземлённые:

  • пользователь может писать на смеси языков;
  • тонкие различия (uk-UA vs ru-RU) плохо детектятся по одному сообщению;
  • ChatGPT уже проделал эту работу за вас и прислал locale.

Автодетект полезен как fallback, если openai/locale приходит в чём‑то странном виде или отсутствует (что сейчас бывает редко), но строить на нём основную логику не стоит. Грубое правило:

  • сначала смотрим на openai/locale как на «истину»;
  • затем учитываем userLocation (валюта, ассортимент);
  • и только в совсем спорных случаях можно дополнительно заглянуть в язык последнего сообщения.

8. Разные комбинации locale и userLocation: таблица сценариев

Для закрепления давайте посмотрим, как GiftGenius должен себя вести в разных сценариях.

Сценарий locale userLocation.country Язык UI Валюта Каталог
1
en-US
US
EN
USD
US‑товары
2
bg-BG
BG
BG
BGN
BG‑товары
3
en
DE
EN
EUR
DE‑товары
4
ru-RU
DE
RU
EUR
DE‑товары
5
en
(нет данных) EN
USD
Global default

Этот взгляд вам пригодится позже, когда мы будем обсуждать commerce, но уже сейчас видно, как легко менять поведение, просто подсовывая разные locale и country.

9. Небольшая диаграмма потока сигналов локали

Чтобы собраться с мыслями, посмотрим на упрощённую схему:

flowchart TD
  U[Пользователь<br/>пишет сообщение] --> C[ChatGPT]
  C -->|определяет| L[openai/locale<br/>+ userLocation]
  L -->|передаёт| W["Widget (Next.js)"]
  L -->|передаёт через _meta| S[MCP Server]

  W -->|locale| UI[GiftGenius UI<br/>тексты + формат чисел]
  S -->|locale + country| DATA[Каталоги, цены, фильтры]

  style L fill:#e0f7ff,stroke:#00a
  style W fill:#f7fff0,stroke:#4b4
  style S fill:#fdf0ff,stroke:#b4

Важно заметить: в этой схеме нигде не нарисовано модальное окно «Выберите язык». Оно нужно только как дополнительный слой, когда сигналы противоречат ожиданиям пользователя.

10. Практика: что можно сделать прямо сейчас в вашем App

Чтобы лекция не осталась теорией, короткий практический чек‑лист по GiftGenius:

  • В виджете: добавить хук useOpenAiGlobal("locale") или его аналог и хотя бы в одном месте завести развилку RU/EN для текста.
  • В MCP‑сервере: в одном из существующих инструментов (suggest_gifts) достать _meta["openai/locale"] и _meta["openai/userLocation"], вывести их в лог и использовать для выбора каталога.
  • Написать простую функцию deriveCurrency(locale, country) и использовать её в одном месте при форматировании цены.

Не нужно сразу строить полный i18n‑движок и 15 языков — наша задача сейчас научиться честно пользоваться сигналами платформы.

11. Типичные ошибки при работе с locale и userLocation

Ошибка №1: полностью игнорировать openai/locale и полагаться только на navigator.language.
Так делают те, кто привык к обычным веб‑приложениям. В ChatGPT пользователь вообще может ничего не открывать в браузере, а navigator.language на вашей стороне — это язык туннельного сервера или Vercel, а не пользователя. В итоге UI всегда «загадочно» на английском, хотя ChatGPT стабильно присылает вам ru-RU.

Ошибка №2: каждый раз спрашивать пользователя «на каком языке удобнее?»
Если у вас в каждом чате первая реплика виджета — опрос языка, пользователи начинают чувствовать себя в аэропорту, где их пять раз подряд спрашивают, не забыли ли они багаж. Платформа уже знает язык и регион — достаточно уважать openai/locale и спрашивать только при явном конфликте (например, запрос на русском при locale = "en").

Ошибка №3: хранить выбранный язык только в UI и не передавать его в MCP‑инструменты.
Виджет может быть на русском, а сервер продолжать отдавать англоязычный каталог, потому что он не знает о смене языка. Всегда думайте о сквозном пути: если в UI есть переключатель, его результат нужно донести до бэкенда — либо в аргументах инструмента, либо через Gateway‑сессию.

Ошибка №4: пытаться «угадать» язык только по тексту сообщений, игнорируя openai/locale.
Автодетект по тексту может работать неплохо… пока у пользователя чистый английский. Как только появятся смешанные языки или сходные фразы, результат начнёт плавать. openai/locale — уже готовая, достаточно надёжная оценка, предоставленная платформой. Её стоит считать основным источником правды, а детект текста — лишь дополнительным сигналом.

Ошибка №5: смешивать бизнес‑логику и локализацию в стиле if (locale === 'bg') { ... } по всему коду.
На этой лекции мы ещё немного так делаем ради простоты, но важно заранее планировать, что строки, форматы и каталоги должны быть отделены от бизнес‑логики. В противном случае через пару месяцев вы окажетесь в коде, где каждая функция начинается с if (locale.startsWith("bg")), а добавить ещё один язык будет больно. В лекции 44 мы будем лечить как раз эту проблему, запоминая, что источник locale у нас уже есть и пользоваться им умеем.

1
Задача
ChatGPT Apps, 9 уровень, 1 лекция
Недоступна
Бейдж локали из платформы (openai/locale)
Бейдж локали из платформы (openai/locale)
1
Задача
ChatGPT Apps, 9 уровень, 1 лекция
Недоступна
MCP-tool “контекст локали” + отображение country/валюты
MCP-tool “контекст локали” + отображение country/валюты
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ