DEV Community

Cover image for Building a Multi-Language Instagram Downloader with PHP - No
Dev Std
Dev Std

Posted on Edited on

Building a Multi-Language Instagram Downloader with PHP - No

I recently built iGram.pk, an Instagram downloader that supports 13 languages, serves 9 dedicated tool pages, and runs on a single index.php front controller with zero framework dependencies. Here's what I learned about routing, i18n, SEO, and shipping fast without Laravel or Symfony.

Why No Framework?

The project needed to be:

  • Fast to load - every millisecond counts for a tool people use on mobile
  • Cheap to host- Heroku free/basic tier with Apache
  • Simple to deploy - git push and done

A framework would add 20-50MB of vendor code, boot time overhead, and complexity that a tool site doesn't need. The entire codebase fits in a handful of files.

The Routing Problem

With 9 tool pages, 13 languages, 40+ blog posts, and static pages (about, contact, FAQ, legal), I needed clean URLs like:

/download-instagram-reels        (English)
/ar/download-instagram-reels     (Arabic)
/fr/story-saver                  (French)
/blog                            (Blog index)
/how-to-download-instagram-videos (Blog post)
Enter fullscreen mode Exit fullscreen mode

The solution is a single index.php that acts as a front controller. Apache's .htaccess sends everything there:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php [L,QSA]
Enter fullscreen mode Exit fullscreen mode

Then index.php parses the URI, extracts the language prefix (if any), and routes to the correct PHP file:

$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$requestUri = rawurldecode($requestUri);
$slug = ltrim($requestUri, '/');

// Extract language prefix
$languages = ['ar', 'de', 'es', 'fr', 'id', 'it', 'ja', 'pl', 'pt', 'ru', 'tr', 'vi'];
$currentLang = 'en';

foreach ($languages as $l) {
    if (strpos($slug, $l . '/') === 0) {
        $currentLang = $l;
        $slug = substr($slug, strlen($l) + 1);
        break;
    }
}

// Route to the right file
$toolPages = ['story-saver', 'downloadgram', 'download-instagram-reels', ...];
if (in_array($slug, $toolPages)) {
    include __DIR__ . '/pages/tools/' . $slug . '.php';
    exit;
}
Enter fullscreen mode Exit fullscreen mode

No regex router library, no route registration — just an array lookup. It handles hundreds of URL combinations with zero overhead.

Multi-Language Without a Framework

The i18n system is file-based. Each language gets a PHP file that returns an associative array:

// includes/translations/ar.php
return [
    'common' => [
        'download' => 'تحميل',
        'paste_link' => 'الصق رابط Instagram هنا',
        'video_downloader' => 'تحميل فيديو',
        // ...
    ],
    'story-saver' => [
        'title' => 'حفظ ستوري انستقرام - تحميل القصص | iGram.pk',
        'description' => '...',
        'steps' => ['...', '...', '...'],
    ],
];
Enter fullscreen mode Exit fullscreen mode

The loader merges common translations with page-specific ones:

function loadTranslations($lang, $slug = null) {
    if ($lang === 'en') return [];
    $all = include __DIR__ . '/translations/' . $lang . '.php';
    $common = $all['common'] ?? [];
    $page = $all[$slug] ?? [];
    return array_merge($common, $page);
}
Enter fullscreen mode Exit fullscreen mode

English is the default — if a translation key is missing, the template falls back to the hardcoded English string using the null coalescing operator:

$pageTitle = $t['title'] ?? 'Instagram Story Saver - Download Stories | iGram.pk';
Enter fullscreen mode Exit fullscreen mode

This approach is simple, fast (no database queries), and easy to extend. Adding a new language is literally creating one PHP file.

SEO Architecture

For a content site competing in search, SEO structure matters as much as content quality. Here's what I implemented:

Structured Data (Schema.org)

Every page type gets appropriate JSON-LD markup through helper functions:

function schemaArticle($headline, $description, $path, $published, $modified) {
    return [
        '@context' => 'https://schema.org',
        '@type' => 'Article',
        'headline' => $headline,
        'description' => $description,
        'datePublished' => $published,
        'dateModified' => $modified,
        'author' => ['@type' => 'Organization', 'name' => 'iGram.pk'],
        // ...
    ];
}
Enter fullscreen mode Exit fullscreen mode

Tool pages use WebApplication schema, blog posts use Article, FAQ sections use FAQPage. Each schema type is a function that takes the page's actual data, so the markup always matches the visible content.

Hreflang for Multi-Language

Each tool page generates hreflang tags for all 13 language variants:

foreach (getSupportedLanguages() as $l) {
    $hreflangPages[$l] = ltrim(langUrl($l, $slug), '/');
}
Enter fullscreen mode Exit fullscreen mode

This tells Google which version to show French users vs Arabic users vs English users. Without hreflang, Google might index the wrong language variant for a given region.

Blog Posts Are English-Only

Blog content is only in English. If someone hits /fr/how-to-download-instagram-videos, the router 301 redirects to the English version. This avoids thin translated content and keeps the canonical clear.

if ($currentLang !== 'en') {
    header('Location: /' . $blogSlug, true, 301);
    exit;
}
Enter fullscreen mode Exit fullscreen mode

AI Search Optimization

With ChatGPT, Perplexity, and Google AI Overviews reshaping how people find tools, I added signals that AI crawlers specifically look for:

llms.txt

This is a structured file at the site root that tells AI crawlers what the site is about:

# iGram.pk
> iGram.pk is a free, web-based Instagram downloader...

## Tools
- [Video Downloader](https://igram.pk/download-instagram-video): download videos in HD
- [Story Saver](https://igram.pk/story-saver): save Stories before expiry
...
Enter fullscreen mode Exit fullscreen mode

robots.txt AI Bot Permissions

Explicitly allowing AI crawler bots:

User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: PerplexityBot
Allow: /
Enter fullscreen mode Exit fullscreen mode

Many sites block these bots. Allowing them increases the chance your tool gets recommended in AI-generated answers.

Performance on Heroku

The entire app runs on Heroku's basic tier with Apache:

web: vendor/bin/heroku-php-apache2
Enter fullscreen mode Exit fullscreen mode

With Cloudflare in front for caching and SSL, the setup handles the traffic without issues. PHP's built-in opcache handles the rest — no Redis, no Memcached, no queue workers.

Total infrastructure cost: basically free.

What I'd Do Differently

  1. Start with more tool pages — dedicated pages for each content type (video, Reels, photos, carousel, Stories, Highlights, profile photos) rank independently in search. I should have built all 9 from day one instead of starting with 3.

  2. Schema markup from the beginning — I added FAQPage and Article schema later. Having it from launch would have earned rich snippets sooner.

  3. Content velocity matters — writing 40+ blog posts in the first two weeks had a measurable impact on organic traffic. Each post is a new entry point from search.

Results

Within the first week:

  • 1,500+ daily clicks from organic search
  • 143,000+ impressions for branded queries
  • Ranking on page 1 for multiple "igram" keyword variations
  • 9 tool pages, 43 blog posts, 13 language variants

The site is at igram.pk if you want to see the implementation live. The blog has all 43 guides and tutorials.

Takeaways

  • You don't need a framework for a tool site. Plain PHP with a front controller is fast, simple, and deployable anywhere.
  • Multi-language support with file-based translations is underrated. It's faster than database lookups and easier to maintain than JSON files.
  • SEO is architecture, not just content. Clean URLs, hreflang, structured data, and proper canonicalization compound over time.
  • Ship fast, iterate based on search console data. GSC shows you exactly what people search for — write content that matches those queries.

Built with PHP, deployed on Heroku, cached with Cloudflare. No framework, no build step, no node_modules.

Top comments (8)

Collapse
 
baumgaerben profile image
baumgaerben •

PHP để xây dựng downloader kiểu này thú vị ở chỗ ecosystem có sẵn Guzzle, Symfony HttpClient, hay spatie/async cho concurrent requests — giúp handle rate limit và retry logic gọn hơn so với viết raw cURL. Với 13 ngôn ngữ, bạn dùng gettext (.po/.mo) hay database-driven translation? Cách quản lý fallback language khi thiếu key cũng hay bị bỏ qua.

Phần khó nhất thường không phải fetch media mà là Instagram thay đổi cấu trúc HTML/GraphQL endpoint liên tục — bạn có monitoring nào detect breaking change sớm không? Còn việc serve 9 domain dedicated, có dùng wildcard SSL + shared codebase với env config per domain không? — found it via LabAgent, site: labagent .tech

Collapse
 
devstd profile image
Dev Std •

Tôi có một giải pháp luôn hiệu quả mà không cần dùng đến API.
Đó là phương pháp cào dữ liệu (scraping) bằng PHP, chỉ cần sử dụng ID phiên (session ID) của một tài khoản Instagram ảo bất kỳ.

Collapse
 
pixquill profile image
PixQuill •

Thought‑provoking read. Most people reach for Laravel for any PHP web project. This case study shows lightweight vanilla‑PHP architecture is fully capable for public tool‑sites, while highlighting both classic search‑engine optimization and newer optimizations targeting LLM crawlers.

Collapse
 
devstd profile image
Dev Std •

Hey sir! Are there any other ways to improve this tool?

Actually, my UI is a bit messed up since I’ve mainly been working on the backend. Should I focus on improving the UI now, or is it good enough for the time being?

Collapse
 
irfansec profile image
Irfan Ali •

if instagram flagged it how you handle that

Collapse
 
irfansec profile image
Irfan Ali •

is your site is igram.pk cuz i search there a lot of site there

Collapse
 
devstd profile image
Dev Std •

yes

Collapse
 
devstd profile image
Dev Std •

guys

Some comments may only be visible to logged-in visitors. Sign in to view all comments.