DEV Community

Cover image for How I Built a Multilingual Instagram Downloader with Pure PHP (No Framework)
Dev Std
Dev Std

Posted on

How I Built a Multilingual Instagram Downloader with Pure PHP (No Framework)

Most developers reach for Laravel or Symfony when starting a PHP project. I went the opposite direction I built iGram.pk, a free Instagram downloader that supports 16 languages, using nothing but vanilla PHP, .htaccess routing, and a clean file structure.

Here's what I learned and how I structured it.

Why No Framework?

For a tool like this, a full framework is overkill. The app does one thing: accept an Instagram URL, fetch the media, and serve it for download. I needed:

  • Fast page loads (no framework overhead)
  • Clean SEO-friendly URLs
  • Multi-language support without bloated i18n libraries
  • Easy deployment on shared hosting or Heroku

Vanilla PHP gave me all of this with a tiny footprint.

The Routing Layer

Instead of a framework router, I used a single index.php as the entry point with .htaccess rewrites:


apache
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

Then in index.php, I parse the URI and route to the right page:

$uri = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
$segments = explode('/', $uri);

// Check if first segment is a language code
$supportedLangs = ['ar', 'bn', 'de', 'es', 'fr', 'id', 
                   'it', 'ja', 'ko', 'pl', 'pt', 'ru', 
                   'th', 'tr', 'vi'];

$lang = 'en';
if (in_array($segments[0], $supportedLangs)) {
    $lang = array_shift($segments);
}

$page = implode('/', $segments) ?: 'home';

No regex libraries. No route definitions file. Just array lookups and string parsing. It handles 260+ URLs with zero dependencies.

Multi-Language Without i18n Libraries

Each language gets a single PHP file that returns a nested array:

// includes/translations/ko.php
return [
    'common' => [
        'site_name' => 'iGram',
        'download_btn' => '다운로드',
        'paste_placeholder' => 'Instagram 링크를 여기에 붙여넣으세요',
        'how_to_title' => '다운로드 방법',
    ],
    'story-saver' => [
        'title' => 'Instagram 스토리 세이버',
        'description' => '공개 Instagram 스토리를 다운로드하세요',
    ],
];

The loader merges common strings with page-specific ones:

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

function t($translations, $key, $fallback = '') {
    return $translations[$key] ?? $fallback;
}

The $fallback parameter is key — English pages just use the fallback strings directly, so there’s no English translation file at all. Less files, less maintenance.

SEO With Structured Data

Every tool page gets proper Schema.org JSON-LD without any library:

function schemaWebApplication($name, $path, $description, $lang = 'en') {
    return [
        '@context' => 'https://schema.org',
        '@type' => 'WebApplication',
        'name' => $name,
        'url' => 'https://igram.pk/' . ltrim($path, '/'),
        'description' => $description,
        'applicationCategory' => 'MultimediaApplication',
        'operatingSystem' => 'Any',
        'inLanguage' => $lang,
        'isAccessibleForFree' => true,
        'offers' => [
            '@type' => 'Offer', 
            'price' => '0', 
            'priceCurrency' => 'USD'
        ],
    ];
}

This plus proper hreflang tags for all 16 languages gives Google clear signals about what each page is and which language it targets.

Hreflang for 16 Languages

Each tool page outputs hreflang links for every language variant:

$languages = getSupportedLanguages(); // returns all 16 codes
foreach ($languages as $code) {
    $href = $code === 'en' ? "/$slug" : "/$code/$slug";
    echo '<link rel="alternate" hreflang="' . $code . '" 
          href="https://igram.pk' . $href . '"/>';
}
echo '<link rel="alternate" hreflang="x-default" 
      href="https://igram.pk/' . $slug . '"/>';

The sitemap mirrors this with xhtml:link entries — 260 URLs total, all with proper language annotations.

Deployment

The whole app runs on Heroku’s free tier with one line in the Procfile:

web: vendor/bin/heroku-php-apache2

No build step. No compilation. Push and it’s live.

Performance Results

Without a framework:

First Byte Time: ~120ms
Total page weight: ~45KB (including CSS and JS)
Lighthouse Performance: 95+
Zero npm dependencies
What I’d Do Differently
Use a simple template engine — I started with raw <?php echo everywhere. Even a 50-line template function would’ve saved time.
Add automated tests earlier — Vanilla PHP makes it easy to skip testing. Don’t.
Plan the translation structure upfront — Adding languages 3 at a time when you started with 13 means retrofitting every page template.
Try It

The tool is live at igram.pk. It supports downloading Instagram videos, Reels, photos, carousel posts, Stories, Highlights, and profile pictures — all free, no login required.

If you’re building a simple web tool, consider whether you actually need that framework. Sometimes plain PHP is the fastest path to production.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)