आपके buttons, nav और empty states German में हैं। आपके product names, categories और descriptions अब भी English में हैं। यह आपके i18n setup का bug नहीं, उसकी सीमा है। globalize.now AI-संचालित स्थानीयकरण इंफ्रास्ट्रक्चर है: यह आपके codebase से hardcoded strings निकालता है, keys और locale catalogs बनाता है और हर Git push पर उन्हें sync करता है। यह code पढ़ता है, rows नहीं—और आपके users जिस content के लिए आते हैं, वह rows में रहता है।

इसे ठीक करना tooling का नहीं, schema का निर्णय है। टिकाऊ तरीका यह है।

मेरे Lovable ऐप का UI अनुवादित है, लेकिन database content अब भी English में क्यों है?

क्योंकि string extractor केवल वही देख सकता है जो आपकी source files में लिखा है।

Lovable ऐप का स्थानीयकरण करते समय scanner आपके components को देखता है और मिलने वाली हर literal string निकालता है: "Add to cart", "No results", "Sign in"। ये catalog में message IDs बन जाती हैं, फिर catalog का अनुवाद करके commit किया जाता है। यह pipeline deterministic है और interface को पूरी तरह cover करती है।

लेकिन यह किसी row को cover नहीं कर सकती। products.name = 'Trail Runner' कभी किसी .tsx file में था ही नहीं। यह किसी form, seed script या import के ज़रिए आया और Postgres में रहता है—Lovable Cloud के built-in backend में, जो Supabase पर चलता है, या आपके द्वारा connect किए गए Supabase project में। कोई code scanner इसे कभी नहीं पढ़ेगा और extraction को बार-बार चलाने से भी यह नहीं बदलेगा।

नतीजा होता है English content के चारों ओर अनुवादित shell। Nav में Warenkorb लिखा है, लेकिन उसके भीतर product है Trail Runner, lightweight trail shoe for wet conditions। Users इसे तुरंत नोटिस करते हैं, और यह untranslated app से भी खराब लगता है, क्योंकि ऐसा लगता है कि काम बीच में छोड़ दिया गया।

Lovable ऐप में database content किसे माना जाता है?

ऐसी हर चीज़ जिसे user या admin deploy किए बिना बदल सकता है। सामान्य Lovable build में इसमें शामिल हैं:

  • Product, plan या listing के names और descriptions
  • Lookup table से render होने वाले category, tag और status labels
  • pages या sections table में stored marketing copy
  • Email और notification templates
  • ऐसी enum-जैसी values जिन्हें आप सीधे display करते हैं, जैसे status = 'pending_review'
  • User-generated content, जिसे आम तौर पर जानबूझकर उसकी मूल भाषा में ही रखा जाता है

आखिरी बिंदु महत्वपूर्ण है। User-generated content का default रूप से अनुवाद न करें। Spanish में लिखी गई review Spanish में ही रहनी चाहिए। आपको वास्तव में उसी content का स्थानीयकरण करना है जिसे आप स्वयं लिखकर ship करते हैं।

बाकी सब—उस content के आसपास का chrome—आपके catalogs में ही रहना चाहिए। इन दोनों sets को कभी overlap नहीं करना चाहिए। अगर कोई label lookup table और hardcoded string, दोनों में मौजूद है, तो उसके लिए एक ही जगह चुनकर दूसरी हटा दें; वरना एक ही concept के लिए German के दो अलग शब्द ship हो जाएँगे।

Supabase में database content के translations कैसे store करें?

Row ID और locale column पर keyed एक अलग translation table इस्तेमाल करें।

create table product_translations (
  product_id  uuid not null references products(id) on delete cascade,
  locale      text not null,
  name        text not null,
  description text,
  primary key (product_id, locale)
);

create index product_translations_locale_idx on product_translations (locale);

अब Japanese जोड़ना migration नहीं, केवल insert है। Row-level security policies बढ़ते हुए columns के समूह के बजाय एक table पर लागू होती हैं, और missing translation एक missing row होती है—जिसे query करना और जिसके लिए alert बनाना आसान है।

विकल्प है हर translatable field के लिए एक JSONB column:

alter table products
  add column name_i18n jsonb not null default '{}'::jsonb;

-- { "en": "Trail Runner", "de": "Trail Runner", "fr": "Coureur de sentier" }

यह prototype बनाने में तेज़ है और छोटे, अधिकतर static content के लिए ठीक है। लेकिन हर भाषा के लिए constraints संभालने पड़ते हैं, partial translations का audit कठिन हो जाता है और localized value पर filter या sort करने लगें तो indexing महँगी पड़ती है।

तीसरा pattern—हर भाषा के लिए अलग column (name_en, name_de, name_fr)—जिससे बचना चाहिए। हर नई भाषा हर table पर schema migration बन जाती है, और Lovable project में इसका मतलब है agent से बार-बार production tables बदलने को कहना। अगर आप इस pattern का विस्तृत विवरण चाहते हैं, तो public pg_i18n Postgres extension fallback के साथ views के रूप में translation-table approach लागू करती है। किसी structure पर commit करने से पहले इसे पढ़ना उपयोगी होगा।

आपको कौन-सा pattern चुनना चाहिए?

Translation table—जब तक content बहुत छोटा और स्थिर न हो।

JSONB तभी चुनें जब translatable fields मुट्ठी भर से कम हों, आपको उन पर filter या sort करने की ज़रूरत न हो और आप उन्हें अपने अलावा किसी और से edit कराने की योजना न रखते हों। बाकी हर स्थिति—admin UI वाला content, आगे भाषाएँ जोड़ने वाला content या कुछ सौ से अधिक rows—के लिए table बेहतर है।

Render time पर सही भाषा को query कैसे करें?

Database में explicit fallback के साथ, ताकि app को इसके बारे में सोचने की ज़रूरत ही न पड़े।

create or replace function products_for_locale(p_locale text)
returns table (id uuid, slug text, name text, description text)
language sql stable as $$
  select
    p.id,
    p.slug,
    coalesce(t.name, base.name)               as name,
    coalesce(t.description, base.description)  as description
  from products p
  left join product_translations base
    on base.product_id = p.id and base.locale = 'en'
  left join product_translations t
    on t.product_id = p.id and t.locale = p_locale;
$$;

इसके बाद call site केवल locale भेजती है, और कुछ नहीं:

const { data: products } = await supabase
  .rpc('products_for_locale', { p_locale: locale });

वास्तविक data के साथ भी इसे टिकाऊ बनाने वाले दो नियम हैं। पहला, coalesce को source language पर fallback करें, कभी empty string पर नहीं: आधा-अनुवादित catalog blank cards के बजाय English दिखाए। दूसरा, जो locale आप भेजते हैं, वही value आपके route segment और UI catalogs की keys में इस्तेमाल होनी चाहिए। एक locale list, तीन consumers: router, catalogs और database।

अधिकांश Lovable projects इसी आखिरी बिंदु पर असंगत हो जाते हैं। Router de को जानता है, catalogs de-DE के लिए बनाए गए हैं और database में rows german से tagged हैं। अपने locale routes में इस्तेमाल होने वाले BCP 47 tags का exact set चुनें और बाकी सबको उसी के अनुरूप करें। अगर आपने अभी routes सेट अप नहीं किए हैं, तो per-stack walkthroughs Lovable के दोनों generated stacks के लिए उपलब्ध हैं: Vite SPA build और TanStack Start SSR default।

Database text का अनुवाद करने के लिए runtime widget ही क्यों न इस्तेमाल करें?

क्योंकि यह आपके data पर नहीं, rendered page पर काम करता है—और यह अंतर तीन जगह दिखाई देता है।

JavaScript translation widget DOM में आने वाले text को पढ़ता है और बाद में उसे बदल देता है। इससे database rows सचमुच पकड़ी जाती हैं: यही वह काम है जो widgets करते हैं और catalogs नहीं, इसलिए शुरुआत में वे complete solution लगते हैं।

लेकिन इसकी लागत संरचनात्मक है। Translated rows केवल browser session में मौजूद रहती हैं, इसलिए page crawl करने वाले search engines आपकी source language देखते हैं; Weglot का अपना help centre भी कहता है कि उसका JavaScript integration SEO benefits नहीं देता, क्योंकि translations client-side render होती हैं। Corrections आपके database के बजाय vendor के dashboard में रहती हैं, इसलिए field की वास्तविक value दो systems में बँट जाती है। और swap paint के बाद होता है, इसी वजह से इस तरह बने translated apps हर navigation पर पहले English दिखाते हैं।

Internal tool के लिए इस approach को पूरी तरह खारिज करने का यह कारण नहीं है। लेकिन public, indexable, multilingual product को इस पर बनाने से बचने का यह पर्याप्त कारण है।

दोनों layers को sync में कैसे रखा जाता है?

एक locale list और एक trigger साझा करके।

Code layer automatic है। अपने Lovable workspace में lovable-i18n skill जोड़ें और Lovable को i18n setup करने के लिए prompt करें, या इसे locally इंस्टॉल करें:

npx skills add globalize-now/globalize-skills

इसके बाद हर push पर extraction चलेगा और translation pull request खुलेगी—कोई dashboard step नहीं, locale files पर कोई manual pass नहीं। यही वह loop है जिसका वर्णन हर push पर Lovable translations क्यों टूटती हैं में किया गया है।

डेटा लेयर आपकी है, और इसमें एक सोच-समझकर जोड़ा गया हुक होना चाहिए: जब भी आप कोई locale जोड़ें, routes और catalogs चलाने वाली वही सूची आपकी translation table में backfill भी करे। Lovable प्रोजेक्ट में इसका व्यावहारिक तरीका है कि आप agent से एक बार seed function लिखवाएँ, जो locale सूची पढ़कर source-language values के आधार पर missing rows डाल दे। इस तरह missing rows भरी जाती हैं, गढ़ी नहीं जातीं, और तब तक coalesce उस कमी को संभाल लेता है।

यहाँ pricing भी आपके रास्ते में नहीं आती। Starter plan की कीमत €20 प्रति workspace प्रति माह है और इसमें हर महीने €20 का translation credit (लगभग 200,000 शब्द) शामिल है; इससे अधिक उपयोग प्रकाशित per-character rate पर बिल किया जाता है। न per-seat शुल्क है, न per-language शुल्क—इसलिए पाँचवीं भाषा जोड़ने पर केवल उतने characters का खर्च आएगा जितने वास्तव में उपयोग होंगे। Signup पर €5 का translation credit मिलता है और card की जरूरत नहीं होती। Agent-built stack में यह कैसे फिट बैठता है, इसकी अधिक जानकारी vibe coders और developers पेजों पर है।

जब कोई localization vendor बंद हो जाता है, तो क्या टूटता है?

उनके servers पर जो कुछ stored था, वही—आपके servers पर stored कुछ भी नहीं।

इस महीने यह कोई काल्पनिक स्थिति नहीं है। Lovalingo, runtime translation layer जिसे कई Lovable builders ने अपनाया था, 31 अगस्त 2026 को 23:59 CEST पर बंद हो रहा है। इसकी पुष्टि उसके अपने service-closure page पर की गई है; नए projects और subscriptions पहले ही रोक दिए गए हैं और active customers के लिए preservation exports तैयार किए जा रहे हैं। इसकी अपनी guidance का सबसे महत्वपूर्ण हिस्सा यह है: customers को इसके साथ-साथ project-owned internationalization लागू करने और live environment में replacement के production checks पास होने के बाद ही इसे हटाने की सलाह दी गई है।

जिस vendor का जाना इस बात को साबित करता है, उसी की ओर से यह पूरा तर्क एक वाक्य में है। Committed catalog और product_translations table—दोनों ऐसी चीजें हैं जिनके मालिक आप हैं; इनका कोई service status नहीं होता। Managed runtime layer आपके product का text render करने वाली dependency है, और dependencies कभी न कभी खत्म होती हैं।

अगर आप अभी किसी widget से migrate कर रहे हैं, तो पहले data layer संभालें। UI strings कुछ ही मिनटों में आपके code से दोबारा extract हो जाएँगी। लेकिन database translations, अगर वे केवल vendor के dashboard में मौजूद थीं, तो वापस नहीं आएँगी। कुछ भी हटाने से पहले क्या जाँचना है, यह हमारी Lovalingo comparison में बताया गया है।

Code layer वह हिस्सा है जिसे आपको हाथ से manage नहीं करना चाहिए। globalize.now आपकी strings extract करता है, catalogs generate करता है और हर push पर translation pull request खोलता है—इससे आप data layer को एक बार design करके भूल सकते हैं।

globalize.now आपके ऐप्लिकेशन की हार्डकोडेड कॉपी को अनुवाद-तैयार locale फ़ाइलों में बदल देता है और जैसे-जैसे आप नई रिलीज़ करते हैं, उन्हें अपडेट रखता है।

globalize.now को निःशुल्क आजमाएं