diff --git a/.eleventy.js b/.eleventy.js index cc6fcf2..19bc592 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -1,4 +1,6 @@ const markdownIt = require("markdown-it"); +const fs = require("fs"); +const nodePath = require("path"); function normalizePathPrefix(rawPathPrefix = "/") { if (!rawPathPrefix || rawPathPrefix === "/") { @@ -24,7 +26,7 @@ module.exports = function (eleventyConfig) { eleventyConfig.addPassthroughCopy({ "assets/img": "img" }); eleventyConfig.addPassthroughCopy({ "assets/css": "css" }); - eleventyConfig.addPassthroughCopy("CNAME"); + eleventyConfig.addPassthroughCopy({ "assets/js": "js" }); eleventyConfig.addPassthroughCopy(".nojekyll"); eleventyConfig.addPassthroughCopy({ "assets/img/favicon.png": "favicon.png" }); @@ -32,10 +34,63 @@ module.exports = function (eleventyConfig) { return new Date(dateObj).toISOString().split('T')[0]; }); + eleventyConfig.addFilter("fileLastModified", function (inputPath, langCode) { + if (!inputPath) { + return null; + } + + try { + const normalizedPath = String(inputPath).replace(/^\.\/+/, ""); + const absolutePath = nodePath.resolve(process.cwd(), normalizedPath); + const stats = fs.statSync(absolutePath); + const locale = langCode === "uz" ? "uz-UZ" : "en-US"; + return new Intl.DateTimeFormat(locale, { + year: "numeric", + month: "long", + day: "numeric" + }).format(stats.mtime); + } catch (_error) { + return null; + } + }); + eleventyConfig.addFilter("urlencode", function (str) { return encodeURIComponent(str); }); + eleventyConfig.addFilter("padStart", function (value, length, char) { + return String(value).padStart(length || 2, char || "0"); + }); + + eleventyConfig.addFilter("displayTutorialTitle", function (value) { + if (typeof value !== "string") { + return value; + } + + const original = value.trim(); + if (!original) { + return value; + } + + let title = original.replace(/\s+/g, " "); + const titleBeforeLeadingStrip = title; + + // Remove common tutorial title wrappers that redundantly include "Python". + title = title.replace(/\s*\(\s*Python\s*\)\s*$/i, ""); + title = title.replace(/\s+(?:to|in|with|for|on)\s+Python$/i, ""); + title = title.replace(/\s+Python$/i, ""); + title = title.replace(/^Python'(?:ga|ni|da|ning)\s+/i, ""); + title = title.replace(/^Python(?:ga|ni|da|ning)\s+/i, ""); + title = title.replace(/^Python\s+/i, ""); + + title = title.trim(); + if (titleBeforeLeadingStrip !== title && /^[a-z]/.test(title)) { + title = title.charAt(0).toUpperCase() + title.slice(1); + } + + return title || original; + }); + eleventyConfig.addFilter("getTutorialNav", function (tutorials, currentUrl) { if (!Array.isArray(tutorials) || !currentUrl) { return { first: null, prev: null, next: null }; @@ -52,7 +107,13 @@ module.exports = function (eleventyConfig) { }); eleventyConfig.addCollection("tutorials", function (collectionApi) { - return collectionApi.getFilteredByGlob("tutorial/**/*.md").sort((a, b) => { + return collectionApi.getFilteredByGlob("content/en/tutorial/**/*.md").sort((a, b) => { + return (a.data.order || 0) - (b.data.order || 0); + }); + }); + + eleventyConfig.addCollection("tutorialsUz", function (collectionApi) { + return collectionApi.getFilteredByGlob("content/uz/tutorial/**/*.md").sort((a, b) => { return (a.data.order || 0) - (b.data.order || 0); }); }); @@ -62,6 +123,7 @@ module.exports = function (eleventyConfig) { }); eleventyConfig.addWatchTarget("./assets/css/"); + eleventyConfig.addWatchTarget("./assets/js/"); eleventyConfig.addWatchTarget("./tailwind.css"); eleventyConfig.setServerOptions({ diff --git a/.eleventyignore b/.eleventyignore index 7502bba..fa28076 100644 --- a/.eleventyignore +++ b/.eleventyignore @@ -2,6 +2,8 @@ README.md CONTRIBUTING.md CODE_OF_CONDUCT.md LICENSE +.github/**/*.md +docs/**/*.md node_modules _site tailwind.css diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 65% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md index 5a49eb9..9e695e1 100644 --- a/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -25,8 +25,15 @@ Thank you for contributing. - Keep tutorials clear, practical, and beginner-friendly. - Use consistent front matter (`layout`, `title`, `order`, `permalink`). +- Place tutorials under `content/{en|uz}/tutorial/{basic|intermediate|advanced}/`. +- Use locale-first permalinks only: + - English tutorial: `/en/tutorial/{slug}/` + - Uzbek tutorial: `/uz/tutorial/{slug}/` +- Use `lang: en` or `lang: uz` in localized pages/tutorials. +- Wrap code-related prose tokens in inline code (for example: `if`, `else`, `for`, `while`, `True`, `False`, `None`, `{}`). - Add descriptive image `alt` text. - Verify internal links and JSON-LD output with `npm run check`. +- Keep localized repository docs under `docs/i18n/uz/` (these are not website pages). ## Pull Request Checklist diff --git a/CNAME b/CNAME deleted file mode 100644 index 73894c6..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -belajarpython.com diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index dcd51e4..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,29 +0,0 @@ -# Code of Conduct - -## Our Commitment - -We are committed to providing a friendly, safe, and inclusive environment for everyone. - -## Expected Behavior - -- Be respectful and constructive in discussions. -- Focus feedback on ideas and implementation. -- Welcome contributors with different backgrounds and experience levels. - -## Unacceptable Behavior - -- Harassment, hate speech, or personal attacks. -- Trolling, intimidation, or repeated disruptive behavior. -- Sharing private information without consent. - -## Enforcement - -Project maintainers may remove, edit, or reject contributions and interactions that violate this code of conduct. - -## Scope - -This code of conduct applies to all project spaces, including issues, pull requests, and discussions. - -## Contact - -If you experience or witness unacceptable behavior, open a private issue or contact the maintainers through the repository contact channels. diff --git a/LICENSE b/LICENSE old mode 100755 new mode 100644 index e1bc751..7271da8 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Belajarpython +Copyright (c) 2026 Valikhujaev Yakhyokhuja Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 6ea915c..2620d7a 100755 --- a/README.md +++ b/README.md @@ -4,44 +4,56 @@ --- -# [Belajarpython](https://www.belajarpython.com/) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/belajarpythoncom/belajarpython.com/blob/main/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md) +# [Belajarpython](https://www.belajarpython.com/) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/codeuzhub/python-lessons/blob/main/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/codeuzhub/python-lessons/blob/main/.github/CONTRIBUTING.md) -Belajarpython adalah situs kolaboratif tutorial pemrograman Python bahasa Indonesia +Belajarpython is a collaborative platform for learning Python. -- **Ikuti Tutorial:** Mulai belajar bahasa pemrograman python dari tingkat dasar dengan tutorial yang dikhususkan untuk pemula agar lebih cepat mempelajari bahasa pemrograman python. -- **Baca Artikel:** Baca artikel terbaru dan terpopuler tentang bahasa pemrograman python dengan beragam topik. Artikel dibuat oleh komunitas dan ditujukan untuk komunitas. -- **Ajukan Pertanyaan:** Ajukan setiap pertanyaan yang anda temui tentang bahasa pemrograman python. Setiap orang di komunitas akan segera menyelesaikan setiap pertanyaan pengguna. +- **Follow Tutorials:** Start learning Python from the basics with beginner-focused tutorials designed to help you progress faster. +- **Read Articles:** Explore the latest and most popular Python articles across a wide range of topics. Content is created by the community for the community. +- **Ask Questions:** Ask any Python-related question you run into. Community members are ready to help you solve it. -## Kontribusi +## Contributing -Belajarpython adalah situs terbuka (open source) yang dikembangkan oleh developer untuk developer. Semua orang baik dari kalangan :trollface: developer, :man: mahasiswa, :older_woman: pengajar, bahkan :baby: anak kecil yang baru mempelajari bahasa pemrograman python bisa ikut memberikan :heart: kontribusinya [disini](./CONTRIBUTING.md) +Belajarpython is an open-source website built by developers for developers. Everyone is welcome to contribute: :trollface: developers, :man: students, :older_woman: educators, and even :baby: young beginners who are just learning Python can share their :heart: contributions [here](https://github.com/codeuzhub/python-lessons/blob/main/.github/CONTRIBUTING.md). -### [Code of Conduct](./CODE_OF_CONDUCT.md) +### [Contribution Guide](https://github.com/codeuzhub/python-lessons/blob/main/.github/CONTRIBUTING.md) -Belajarpython telah mengadopsi Kode Etik yang kami harapkan akan diikuti oleh peserta proyek terbuka ini. Mohon baca [full text](./CODE_OF_CONDUCT.md) sehingga anda dapat mengerti aksi apa yang yang bisa dan tidak bisa ditoleransi. - -### [Panduan Kontribusi](./CONTRIBUTING.md) - -Baca [panduan kontribusi](./CONTRIBUTING.md) dari kami untuk mempelajari tentang proses pengembangan konten disini. +Read our [contribution guide](https://github.com/codeuzhub/python-lessons/blob/main/.github/CONTRIBUTING.md) to learn about the content development workflow. ### Good First Issues -Untuk membantu Anda membuat kaki Anda basah dan membuat Anda terbiasa dengan proses kontribusi kami, kami memiliki daftar [good first issues](https://github.com/belajarpythoncom/belajarpython.com/issues) yang mengandung bug yang memiliki lingkup yang relatif terbatas. Ini adalah tempat yang bagus untuk memulai. +To help you get started and become familiar with our contribution process, we maintain a list of [good first issues](https://github.com/codeuzhub/python-lessons/issues) with relatively small scope. It is a great place to begin. -## Pengembangan Lokal +## Local Development ```bash npm ci npm run dev ``` -Build produksi: +Production build: ```bash npm run build npm run check ``` +## Content Structure + +- English homepage: `content/en/index.md` (`/en/`) +- Uzbek homepage: `content/uz/index.md` (`/uz/`) +- English tutorials: `content/en/tutorial/{basic,intermediate,advanced}/*.md` +- Uzbek tutorials: `content/uz/tutorial/{basic,intermediate,advanced}/*.md` + +Canonical tutorial URLs: + +- English: `/en/tutorial/{slug}/` +- Uzbek: `/uz/tutorial/{slug}/` + +Root `/` redirects to `/en/`. + +Uzbek repository docs are stored in `docs/i18n/uz/` (repo-only, not website pages). + ### License Belajarpython is [MIT licensed](./LICENSE). diff --git a/_includes/editor-helpers.njk b/_includes/editor-helpers.njk new file mode 100644 index 0000000..0c675b6 --- /dev/null +++ b/_includes/editor-helpers.njk @@ -0,0 +1,238 @@ + + diff --git a/_includes/footer.njk b/_includes/footer.njk new file mode 100644 index 0000000..3965a16 --- /dev/null +++ b/_includes/footer.njk @@ -0,0 +1,121 @@ +{% set footerVariant = footerVariant or 'compact' %} +{% set currentUrl = page.url or '/' %} +{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') %} +{% set homeUrl = '/uz/' if isUzPage else '/en/' %} +{% set tutorialUrl = '/uz/tutorial/what-is-python/' if isUzPage else '/en/tutorial/what-is-python/' %} +{% set githubRepoUrl = 'https://github.com/codeuzhub/python-lessons' %} +{% set githubProfileUrl = 'https://github.com/yakhyo' %} +{% set linkedinUrl = 'https://www.linkedin.com/in/y-valikhujaev/' %} +{% set youtubeUrl = 'https://www.youtube.com/@codeuz' %} +{% set telegramUrl = 'https://t.me/valikhujaev' %} +{% set contributeUrl = githubRepoUrl + '/blob/main/.github/CONTRIBUTING.md' %} +{% set issueUrl = githubRepoUrl + '/issues' %} +{% set siteLabel = "Python Lessons" %} +{% set taglineText = "Python tutorials in English and Uzbek." if not isUzPage else "Python darslari ingliz va o'zbek tillarida." %} +{% set quickLinksLabel = "Quick Links" if not isUzPage else "Tezkor havolalar" %} +{% set projectLabel = "Project" if not isUzPage else "Loyiha" %} +{% set homeLabel = "Home" if not isUzPage else "Bosh sahifa" %} +{% set tutorialLabel = "Tutorials" if not isUzPage else "Darslar" %} +{% set toolsLabel = "Online IDE" if not isUzPage else "Onlayn IDE" %} +{% set joinGithubLabel = "Join on GitHub" if not isUzPage else "GitHub'da qo'shiling" %} +{% set contributeLabel = "Contributing Guide" if not isUzPage else "Hissa qo'shish qo'llanmasi" %} +{% set issueLabel = "Report an Issue" if not isUzPage else "Muammo haqida yozing" %} +{% set compactTutorialLabel = "Tutorial Index" if not isUzPage else "Darslar ro'yxati" %} +{% set licenseLabel = "MIT Licensed" if not isUzPage else "MIT litsenziyasi asosida" %} +{% set brandLabel = "code uz" %} +{% set compactFooterShowLinks = compactFooterShowLinks if compactFooterShowLinks is defined else true %} + +{% if footerVariant == 'full' %} + +{% else %} + +{% endif %} diff --git a/_includes/head.njk b/_includes/head.njk index b45d725..360002e 100644 --- a/_includes/head.njk +++ b/_includes/head.njk @@ -1,10 +1,11 @@ {% set langCode = lang or 'en' %} + {% set tutorialDisplayTitle = (title | displayTutorialTitle) if title else title %} {% set pageDescription = description or site.description %} {% if isHomepage %} {% set pageTitle = "Python Programming Tutorial | " + site.name %} {% elif isTutorial %} - {% set pageTitle = title + " - Complete Tutorial Basic to Advanced" %} + {% set pageTitle = tutorialDisplayTitle + " - Complete Tutorial Basic to Advanced" %} {% elif title %} {% set pageTitle = title + " | " + site.name %} {% else %} @@ -71,14 +72,14 @@ {"@context":"https://schema.org","@type":"WebSite","@id":"{{ site.url }}/#website","url":"{{ site.url }}","name":"{{ site.name }}","description":"{{ pageDescription }}","inLanguage":"{{ 'uz-UZ' if langCode == 'uz' else 'en-US' }}"} {% else %} {% endif %} diff --git a/_includes/landing.njk b/_includes/landing.njk index 082965d..ec58439 100644 --- a/_includes/landing.njk +++ b/_includes/landing.njk @@ -1,5 +1,8 @@ -
- {% set tutorialList = tutorialItems or collections.tutorials %} +
+ {% set currentUrl = page.url or '/' %} + {% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') %} + {% set defaultTutorialList = collections.tutorialsUz if (isUzPage and collections.tutorialsUz) else collections.tutorials %} + {% set tutorialList = tutorialItems or defaultTutorialList %}

Python Tutorial Content List

@@ -9,29 +12,38 @@
-
+
+ md:pb-0 no-scrollbar px-2 md:px-0">
@@ -43,7 +55,7 @@
- + Basic Level

Basic Level

@@ -61,15 +73,15 @@
{% for tutorial in tutorialList %} - {% set tutorialLevel = "dasar" %} + {% set tutorialLevel = "basic" %} {% if tutorial.data.order > 32 %} {% set tutorialLevel = "advanced" %} {% elif tutorial.data.order > 21 %} - {% set tutorialLevel = "menengah" %} + {% set tutorialLevel = "intermediate" %} {% endif %} - {{ tutorial.data.order }}. - {{ tutorial.data.title }} + {{ tutorial.data.order | padStart }}. + {{ tutorial.data.title | displayTutorialTitle }} {% endfor %}
@@ -86,26 +98,29 @@ const levelIcon = document.getElementById('level-icon'); // Level content configuration const levelContent = { - dasar: { + basic: { title: 'Basic Level', description: 'Start your Python adventure from the basics! Learn fundamental concepts like variables, data types, operators, and contr' + 'ol structures. Suitable for beginners new to programming.', - iconClass: 'fa-solid fa-medal text-[56px] md:text-[80px] leading-none', - iconColor: '#cd7f32' + iconSrc: "{{ '/img/icons/bronze.svg' | url }}", + iconAlt: "Basic Level", + iconClass: 'w-14 md:w-20 h-14 md:h-20' }, - menengah: { + intermediate: { title: 'Intermediate Level', description: 'Develop your Python skills further. Master concepts like modules, file I/O, exceptions, and Object-Oriented Programming ' + '(OOP). Aimed at those who understand Python basics and want to expand their capabilities.', - iconClass: 'fa-solid fa-medal text-[56px] md:text-[80px] leading-none', - iconColor: '#9ca3af' + iconSrc: "{{ '/img/icons/silver.svg' | url }}", + iconAlt: "Intermediate Level", + iconClass: 'w-14 md:w-20 h-14 md:h-20' }, advanced: { title: 'Advanced Level', description: 'Dive deep into advanced Python techniques. Learn type hints, decorators, iterators, generators, and professional program' + 'ming techniques like multithreading and async/await.', - iconClass: 'fa-solid fa-medal text-[56px] md:text-[80px] leading-none', - iconColor: '#d4af37' + iconSrc: "{{ '/img/icons/gold.svg' | url }}", + iconAlt: "Advanced Level", + iconClass: 'w-14 md:w-20 h-14 md:h-20' }, }; function filterTutorials(selectedLevel) { // Update level content @@ -120,8 +135,9 @@ levelDescription.textContent = content.description; if (levelIcon) { + levelIcon.src = content.iconSrc; + levelIcon.alt = content.iconAlt; levelIcon.className = content.iconClass; - levelIcon.style.color = content.iconColor; } // Filter tutorials tutorialItems.forEach(item => { @@ -136,18 +152,18 @@ tab.addEventListener('click', function () { // Update active tab styles tabs.forEach(t => { t.classList.remove( - 'active', 'bg-primary-800', 'text-white', 'border-primary-900', 'shadow-lg', 'transform', 'scale-110', 'hover:scale-115' + 'active', 'bg-primary-800', 'text-white', 'border-primary-900', 'shadow-lg', 'transform', 'scale-110', 'hover:scale-[1.15]' ); t.classList.add('bg-slate-200', 'text-slate-600', 'border-slate-300', 'hover:bg-slate-300'); }); this.classList.add( - 'active', 'bg-primary-800', 'text-white', 'border-primary-900', 'shadow-lg', 'transform', 'scale-110', 'hover:scale-115' + 'active', 'bg-primary-800', 'text-white', 'border-primary-900', 'shadow-lg', 'transform', 'scale-110', 'hover:scale-[1.15]' ); this.classList.remove('bg-slate-200', 'text-slate-600', 'border-slate-300', 'hover:bg-slate-300'); filterTutorials(this.dataset.level); }); }); // Initialize with default level - filterTutorials('dasar'); + filterTutorials('basic'); }); diff --git a/_includes/navbar.njk b/_includes/navbar.njk index f55afa4..ce47542 100644 --- a/_includes/navbar.njk +++ b/_includes/navbar.njk @@ -1,31 +1,125 @@ -