नीचे दिया i18n सेक्शन वही है जिसे आपको AGENTS.md में पेस्ट करना है: दस छोटे नियम जो कोडिंग एजेंट से कहते हैं कि यूज़र को दिखने वाली हर स्ट्रिंग अनुवाद फ़ंक्शन से गुज़ारे, उसे फ़ीचर के हिसाब से key करे, टुकड़ों को कभी न जोड़े, ternary से नकली plural कभी न बनाए, और generated locale फ़ाइल को कभी न छुए। globalize.now एआई-संचालित स्थानीयकरण इंफ्रास्ट्रक्चर है, इसलिए हम एजेंट के लिखे बहुत सारे i18n कोड पढ़ते हैं, और लगभग सारी गड़बड़ियाँ उन्हीं गिनी-चुनी ग़लतियों से आती हैं। हर नियम इन्हीं में से किसी एक से जुड़ा है।
इस पोस्ट का दूसरा हिस्सा इस बारे में है कि यह फ़ाइल कहाँ काम करना बंद कर देती है। डेवलपर्स जिन कोड सरफ़ेस का सबसे ज़्यादा इस्तेमाल करते हैं, उनमें से दो इसे कभी पढ़ते ही नहीं, और नियमों के शब्द बदलने से यह समस्या ठीक नहीं होती।
AGENTS.md के i18n सेक्शन में क्या लिखा होना चाहिए?
यही ब्लॉक। तीन फ़ाइल पाथ और lint कमांड को अपने प्रोजेक्ट के अनुसार बदलें, बाकी जैसा है वैसा रहने दें।
## Internationalization
- Every user-visible string goes through the translation function. That
includes JSX text, input hint text, `title`, `aria-label`, `alt`, toast and
error messages, empty states and the document `<title>`.
- The source locale is `en`. The catalog is `locales/en.json`. Add the key in
the same change that uses it.
- Keys are namespaced by feature: `checkout.summary.total`, not `total3`.
Search the catalog for an existing key before creating one.
- Never concatenate translated fragments. Use interpolation:
`t('cart.items', { count })`, not `t('cart.you_have') + count`.
- Plurals use the library's plural forms (`_one` / `_other` keys, or ICU
`{count, plural, ...}`). Never `count === 1 ? 'item' : 'items'`.
- Dates, numbers and currency go through `Intl.DateTimeFormat` and
`Intl.NumberFormat` with the active locale. Never a bare
`toLocaleString()`, never a currency symbol glued to a number.
- Do not hardcode the locale list, language names or text direction in
components. Read them from `i18n/config.ts`.
- Do not translate brand names, code identifiers, URLs or environment values.
- Do not edit locale files other than `en`. They are generated. If a
translation looks wrong, fix the source key or the glossary, not the file.
- Before finishing, run `npm run lint`. The `i18next/no-literal-string`
rule must pass.
दस नियम रखना जान-बूझकर किया गया है। Cursor के अपने rules दस्तावेज़ कहते हैं कि नियमों को उन पैटर्न पर केंद्रित रखें जिन्हें आप बार-बार इस्तेमाल करते हैं और स्टाइल गाइड पेस्ट करने के बजाय linter का उपयोग करें, और Claude Code का मार्गदर्शन भी यही है: निर्देश इतने ठोस लिखें कि उन्हें जाँचा जा सके, और फ़ाइल छोटी रखें क्योंकि वह जितनी बढ़ती है, पालन उतना घटता है। चालीस बुलेट वाले i18n सेक्शन को लोग सरसरी नज़र से ही पढ़ते हैं।
सिर्फ़ "i18n इस्तेमाल करें" लिखने के बजाय ये दस नियम ही क्यों?
क्योंकि <Button>Save changes</Button> लिखते समय एजेंट पहले से मानकर चलता है कि वह यही कर रहा है, यानी "i18n इस्तेमाल करें"। अस्पष्ट निर्देशों को एजेंट अपनी ही समझ के अनुपालन से पूरा मान लेता है। नीचे का हर नियम एक ख़ास आउटपुट का नाम लेकर उसे मना करता है।
Attributes में literals। एजेंट सीख लेते हैं कि JSX children का अनुवाद ज़रूरी है, और फिर aria-label="Close" और alt="Company logo" को सादी स्ट्रिंग की तरह लिख देते हैं, क्योंकि attributes उन्हें कॉन्फ़िगरेशन जैसे दिखते हैं, कॉपी जैसे नहीं। attributes के नाम गिनाने से यह कमी बंद होती है; सामान्य "सब कुछ अनुवाद करें" से नहीं होती।
Concatenation। t('greeting') + ' ' + name + '!' अंग्रेज़ी में ठीक दिखता है, पर ऐसी किसी भी भाषा में उसका अनुवाद नहीं हो सकता जिसमें नाम पहले आता हो या जिसमें नाम का रूप बदलता हो। Interpolation अनुवादक को एक ही स्ट्रिंग देता है, जिसमें उसे सिर्फ़ एक वेरिएबल की जगह बदलनी होती है।
Ternary plurals। i18n सेटअप हो जाने के बाद एजेंट को सबसे ज़्यादा यही लिखते हुए हम देखते हैं: count === 1 ? 'item' : 'items', और यह दो से अधिक plural रूपों वाली हर भाषा के लिए ग़लत है। नियम इस पैटर्न का नाम लेता है ताकि एजेंट इसे पहचानकर बच सके। अगर आपका कैटलॉग ICU इस्तेमाल करता है, तो ICU MessageFormat क्या है और AI-जनित ऐप्स में यह कहाँ टूटता है में सिंटैक्स समझाया गया है।
Generated locale फ़ाइलें। यह नियम इसलिए है क्योंकि एजेंट मददगार होते हैं। जर्मन की कोई टाइपो ठीक करने को कहने पर एजेंट locales/de.json खोलकर उसे एडिट कर देगा, जो तब तक चलता है जब तक अगला अनुवाद जॉब फ़ाइल को सोर्स से दोबारा जनरेट नहीं कर देता। उसे यह बताना कि गैर-सोर्स फ़ाइलें generated हैं और असली सुधार कहाँ होना चाहिए, चुपचाप आने वाले रिग्रेशन की एक पूरी श्रेणी रोक देता है।
उसी बदलाव में en एडिट करना। इसके बिना एजेंट t('checkout.summary.total') जोड़कर आगे बढ़ जाता है, और जब तक कोई ध्यान नहीं देता, key अपने ही नाम के रूप में रेंडर होती रहती है। कैटलॉग की एंट्री और उसका पहला उपयोग एक ही diff में होने चाहिए।
हर टूल असल में यह फ़ाइल कहाँ पढ़ता है?
एजेंट के अनुसार वही कंटेंट अलग-अलग जगहों पर रखा जाता है, और जगह तय करती है कि नियम हमेशा कॉन्टेक्स्ट में रहेगा या सिर्फ़ ज़रूरत पड़ने पर लोड होगा।
रिपॉज़िटरी रूट की AGENTS.md साझा स्थान है। यह फ़ॉर्मैट सादा Markdown है जिसमें कोई अनिवार्य फ़ील्ड नहीं है, इसे Linux Foundation के अंतर्गत Agentic AI Foundation संभालता है, और इसे Cursor, Codex, Copilot कोडिंग एजेंट और कई अन्य टूल पढ़ते हैं। स्पेक में नेस्टेड फ़ाइलें भी समर्थित हैं, जिनमें एडिट की जा रही फ़ाइल के सबसे नज़दीक वाली फ़ाइल को प्राथमिकता मिलती है।
Cursor AGENTS.md को सीधे पढ़ता है, नेस्टेड फ़ाइलों समेत, और इसका अपना स्कोप्ड फ़ॉर्मैट भी है। UI फ़ाइलों तक स्कोप किया गया नियम तब तक कॉन्टेक्स्ट से बाहर रहता है जब तक कोई मेल खाती फ़ाइल खुली न हो:
---
description: i18n rules for user-visible strings
globs: src/**/*.tsx, src/**/*.jsx
alwaysApply: false
---
(paste the Internationalization section here)
फ़ाइल का अंत .mdc पर होना चाहिए और वह .cursor/rules/ में रहनी चाहिए; उस फ़ोल्डर में रखी सादी .md फ़ाइल अनदेखी कर दी जाती है, क्योंकि उसमें frontmatter नहीं होता।
VS Code में GitHub Copilot के तीन विकल्प हैं। .github/copilot-instructions.md हर चैट अनुरोध पर लागू होती है। फ्रंटमैटर में applyTo: "**/*.tsx,**/*.jsx" वाली .github/instructions/i18n.instructions.md फ़ाइल सिर्फ़ तब लागू होती है जब वे फ़ाइलें बनाई या बदली जाएँ। और रिपॉज़िटरी रूट की AGENTS.md को लोकल एजेंट तब पढ़ता है जब chat.useAgentsMdFile सेटिंग ऑन हो; सबफ़ोल्डरों की नेस्टेड AGENTS.md फ़ाइलें एक अलग प्रायोगिक सेटिंग chat.useNestedAgentsMdFiles के पीछे हैं, जो डिफ़ॉल्ट रूप से ऑफ़ रहती है। Copilot से जुड़ा व्यवहार GitHub Copilot से बने ऐप में i18n कैसे जोड़ें में विस्तार से दिया गया है।
Claude Code CLAUDE.md को पढ़ता है, और v2.1.277 से, जब वर्किंग डायरेक्टरी के ऊपर कोई CLAUDE.md न हो तो रूट की AGENTS.md को अपने-आप पढ़ लेता है। अगर आप दोनों रखते हैं, तो दस्तावेज़ों में बताया गया पैटर्न यह है कि CLAUDE.md के शीर्ष पर एक @AGENTS.md इम्पोर्ट रखें ताकि फ़ाइल सिर्फ़ एक बार लोड हो। स्कोपिंग के लिए, frontmatter में paths: सूची वाली .claude/rules/i18n.md तभी लोड होती है जब Claude कोई मेल खाती फ़ाइल पढ़े। Claude Code का पूरा वर्कफ़्लो Claude Code से बने ऐप का स्थानीयकरण कैसे करें में है।
जिस रिपॉज़िटरी को कई एजेंट छूते हैं, उसके लिए व्यावहारिक व्यवस्था यह है: दस नियम उस एजेंट की स्कोप्ड फ़ाइल में रखें जिसे आपकी टीम सबसे ज़्यादा इस्तेमाल करती है, और AGENTS.md में दो पंक्तियों का संकेत रखें ("UI स्ट्रिंग्स: .cursor/rules/i18n.mdc में दिए i18n नियम देखें और उनका पालन करें") ताकि स्कोपिंग सपोर्ट न करने वाले एजेंट को भी निर्देश मिल जाए।
कौन-से कोड सरफ़ेस AGENTS.md को कभी नहीं पढ़ते?
इनलाइन कम्प्लीशन। दोनों वेंडर इसे दस्तावेज़ों में बताते हैं, और यही वजह है कि नियम फ़ाइल पूरा समाधान नहीं हो सकती।
Cursor के rules FAQ में "क्या नियम Cursor Tab या अन्य AI फ़ीचर्स को प्रभावित करते हैं?" का सीधा जवाब "नहीं" है। नियम Agent को फ़ीड होते हैं; Tab, यानी जो ऑटोकम्प्लीट आपकी टाइप की जा रही पंक्ति को पूरा करता है, उन्हें देखता ही नहीं। VS Code के custom-instructions पेज पर Copilot के लिए भी यही नोट है: एडिटर में टाइप करते समय आने वाले इनलाइन सुझावों में निर्देशों को ध्यान में नहीं लिया जाता।
यानी जिस सरफ़ेस पर डेवलपर <p>No results found</p> टाइप करके ghost text स्वीकार कर लेता है, नियम ठीक उसी तक कभी नहीं पहुँचते। चैट और एजेंट मोड स्ट्रिंग को key कर देंगे; पर वह कम्प्लीशन नहीं करेगा जिसने आपके किसी और बात पर सोचते समय पंक्ति पूरी कर दी। सामान्य काम के एक हफ़्ते में कोडबेस में उसी एक रास्ते से literals जमा होते जाते हैं जिसे instruction फ़ाइल कवर नहीं करती, और डेवलपर, जिसने सावधानी से नियम फ़ाइल लिखी थी, मान लेता है कि एजेंट उसे अनदेखा कर रहा है।
Claude Code में इनलाइन कम्प्लीशन सरफ़ेस नहीं है, पर इसके दस्तावेज़ दूसरी तरफ़ से यही बात कहते हैं: CLAUDE.md की सामग्री कॉन्टेक्स्ट है, लागू की गई कॉन्फ़िगरेशन नहीं, और मॉडल के फ़ैसले की परवाह किए बिना किसी कार्रवाई को रोकना हो तो hook इस्तेमाल करें। यहाँ के हर टूल के लिए सही मानसिक मॉडल यही है। Instruction फ़ाइलें संभावनाओं को झुकाती हैं। वे नियम को लागू नहीं करतीं।
तो नियम को असल में लागू कौन करता है?
CI में lint नियम, और जब नियम फ़ाइल एजेंट से पहले ही उसे चलाने को कहती है, तब यह बस एक पंक्ति का जोड़ है। eslint-plugin-i18next के साथ no-literal-string आता है; इसे अपनी कॉम्पोनेंट डायरेक्टरी के लिए ऑन कर दें, और JSX में कोई literal आते ही बिल्ड विनम्र याद दिलाने के बजाय फ़ेल हो जाएगा। सेटअप, नियम के विकल्प, और instruction व enforcement दो अलग परतें क्यों हैं, यह सब i18n सेटअप के बाद भी Cursor हार्डकोड स्ट्रिंग्स क्यों जोड़ता रहता है में समझाया गया है, इसलिए इस पोस्ट में हम इसे नहीं दोहराएँगे।
lint परत आपको दो चीज़ें देती है जो नियम फ़ाइल नहीं दे सकती। पहली, यह इनलाइन-कम्प्लीशन के आउटपुट को पकड़ लेती है, क्योंकि यह बातचीत पर नहीं, फ़ाइल पर चलती है। दूसरी, यह एजेंट के अपने लूप को ही समाधान बना देती है: ऊपर के ब्लॉक का आख़िरी नियम एजेंट से कहता है कि काम पूरा करने से पहले lint चलाए, और जो एजेंट no-literal-string को फ़ेल होते देखता है वह उसी सेशन में स्ट्रिंग को खुद key कर देगा।
ख़ास तौर पर Claude Code के लिए, PreToolUse या पोस्ट-एडिट hook जो अभी लिखी गई फ़ाइल पर linter चलाए, वही enforcement तंत्र है जिसकी ओर इसके दस्तावेज़ इशारा करते हैं। Hooks तय बिंदुओं पर शेल कमांड की तरह चलते हैं और तब भी लागू होते हैं जब मॉडल ने नियम मानना न चुना हो।
जो स्ट्रिंग्स फिर भी निकल जाती हैं, उनका क्या होता है?
उन्हें extract करना, मौजूदा namespaces के हिसाब से key करना और उनका अनुवाद करना होता है, और यही वह हिस्सा है जिसे बार-बार प्रॉम्प्ट करने के बजाय ऑटोमेट करना सही है। लाल बिल्ड बताता है कि कोई literal मौजूद है। फिर भी किसी को उसे key में बदलना, en में जोड़ना और बाकी हर locale तक पहुँचाना पड़ता है।
globalize.now इसी परत पर काम करता है। रूपांतरण एक बार का होता है और ऐप के भीतर ही होता है: रिपॉज़िटरी कनेक्ट करें, और कोडबेस एक बार कन्वर्ट हो जाता है, कैटलॉग एक pull request के रूप में मिलता है जिसकी आप समीक्षा करते हैं। इसके बाद push जॉब्स नई कैटलॉग इकाइयों का अनुवाद उनके आते ही कर देते हैं, इसलिए मंगलवार के PR में जोड़ी गई key के अनुवाद बुधवार के PR में मिल जाते हैं। रनटाइम लाइब्रेरी, कैटलॉग फ़ॉर्मैट और ऊपर की नियम फ़ाइल, ये सब आपके ही रहते हैं; डेवलपर रेफ़रेंस बताता है कि Cursor, Claude Code, Codex और Copilot के एजेंट सेटअप के बाद की भूमिका कैसे संभालते हैं, और Cursor इंटीग्रेशन सबसे छोटा वॉकथ्रू है।
दो संबंधित विफलताओं पर अलग पोस्ट हैं। अगर समस्या यह है कि एजेंट पहले से key की गई कॉपी को दोबारा लिख देता है, तो वह AI एजेंट्स को आपकी UI कॉपी दोबारा लिखने से कैसे रोकें है, और समाधान वही सोर्स-locale अनुशासन है जो ऊपर के नियम नौ में है। अगर locales मौजूद हैं पर आपस में बिखरते जाते हैं, तो अनुवाद फ़ाइलें सिंक से बाहर क्यों हो जाती हैं इसके पीछे की routing विफलता समझाता है।
शुरुआत कहाँ से करें?
ब्लॉक पेस्ट करें, इसे अपनी UI डायरेक्टरी तक स्कोप करें, और उसी दोपहर CI में no-literal-string ऑन कर दें। फिर एक हफ़्ते तक देखें कि lint नियम क्या पकड़ता है; यही आपका पैमाना है कि नियम फ़ाइल अकेले कितना कर पाती। अगर आप AI से बना ऐप शिप कर रहे हैं और चाहते हैं कि जो कुछ फिर भी निकल जाए उसका extraction और अनुवाद आपको मेंटेन न करना पड़े, बल्कि कोई और संभाले, तो vibe coders पेज में पूरा अवलोकन है और प्राइसिंग का अपना अलग पेज है।
globalize.now आपके ऐप्लिकेशन की हार्डकोडेड कॉपी को अनुवाद-तैयार locale फ़ाइलों में बदल देता है और जैसे-जैसे आप नई रिलीज़ करते हैं, उन्हें अपडेट रखता है।
globalize.now को निःशुल्क आजमाएं