diff --git a/assets/styles/_rtl.scss b/assets/styles/_rtl.scss new file mode 100644 index 000000000..da16f864a --- /dev/null +++ b/assets/styles/_rtl.scss @@ -0,0 +1,407 @@ +/* RTL (Right-to-Left) Support Styles + ========================================================================== */ + +/* RTL Base Styles */ +.rtl { + direction: rtl; + text-align: right; + unicode-bidi: embed; +} + +/* RTL CSS Custom Properties */ +.rtl { + --margin-start: 0; + --margin-end: 0; + --padding-start: 0; + --padding-end: 0; + --border-start: 0; + --border-end: 0; +} + +/* RTL Typography */ +.rtl { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +/* RTL Navigation */ +.rtl .navbar-nav { + padding-right: 0; +} + +.rtl .navbar-nav .nav-item { + margin-left: 0; + margin-right: 20px; +} + +.rtl .navbar-nav .nav-item:last-child { + margin-right: 0; +} + +.rtl .navbar-nav .nav-link { + text-align: right; +} + +/* RTL Dropdowns */ +.rtl .dropdown-menu { + right: 0; + left: auto; + text-align: right; +} + +.rtl .dropdown-item { + text-align: right; +} + +.rtl .dropdown-toggle::after { + margin-left: 0; + margin-right: 0.255em; +} + +/* RTL Forms */ +.rtl .form-control { + text-align: right; +} + +.rtl .form-label { + text-align: right; +} + +.rtl .form-text { + text-align: right; +} + +.rtl .form-check { + padding-left: 0; + padding-right: 1.25em; +} + +.rtl .form-check-input { + margin-left: 0; + margin-right: -1.25em; +} + +.rtl .form-check-label { + text-align: right; +} + +/* RTL Input Groups */ +.rtl .input-group > .form-control:not(:last-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} + +.rtl .input-group > .form-control:not(:first-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.rtl .input-group-text { + border-left: 0; + border-right: 1px solid var(--bs-border-color); +} + +.rtl .input-group > .form-control:not(:last-child) { + border-left: 1px solid var(--bs-border-color); + border-right: 0; +} + +/* RTL Buttons */ +.rtl .btn-group > .btn:not(:last-child):not(.dropdown-toggle) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} + +.rtl .btn-group > .btn:not(:first-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +/* RTL Icons */ +.rtl i { + margin-right: 0; + margin-left: 0.25em; +} + +.rtl .ux-icon { + margin-right: 0; + margin-left: 0.25em; +} + +/* RTL Tables */ +.rtl .table th, +.rtl .table td { + text-align: right; +} + +/* RTL Alerts */ +.rtl .alert { + text-align: right; +} + +/* RTL Modals */ +.rtl .modal-header .close { + margin: -1rem auto -1rem -1rem; +} + +.rtl .modal-footer { + justify-content: flex-start; +} + +/* RTL Pagination */ +.rtl .pagination { + justify-content: flex-end; +} + +.rtl .page-link { + margin-left: 0; + margin-right: -1px; +} + +/* RTL Breadcrumbs */ +.rtl .breadcrumb-item + .breadcrumb-item::before { + content: var(--bs-breadcrumb-divider, "/"); + float: right; + padding-left: 0.5rem; + padding-right: 0; +} + +.rtl .breadcrumb-item + .breadcrumb-item { + padding-left: 0; + padding-right: 0.5rem; +} + +/* RTL List Groups */ +.rtl .list-group-item { + text-align: right; +} + +/* RTL Cards */ +.rtl .card-header { + text-align: right; +} + +.rtl .card-footer { + text-align: right; +} + +/* RTL Badges */ +.rtl .badge { + margin-left: 0; + margin-right: 0.25em; +} + +/* RTL Nav Tabs */ +.rtl .nav-tabs .nav-link { + margin-right: 0; + margin-left: 2px; +} + +/* RTL Progress Bars */ +.rtl .progress-bar { + float: right; +} + +/* RTL Tooltips */ +.rtl .tooltip { + text-align: right; +} + +/* RTL Popovers */ +.rtl .popover { + text-align: right; +} + +/* RTL Spinners */ +.rtl .spinner-border, +.rtl .spinner-grow { + margin-left: 0; + margin-right: 0.5rem; +} + +/* RTL Close Buttons */ +.rtl .btn-close { + margin: -0.5rem auto -0.5rem -0.5rem; +} + +/* RTL Custom Select */ +.rtl .form-select { + background-position: left 0.75rem center; + padding-left: 2.25rem; + padding-right: 0.75rem; +} + +/* RTL Floating Labels */ +.rtl .form-floating > .form-control, +.rtl .form-floating > .form-select { + text-align: right; +} + +.rtl .form-floating > label { + left: auto; + right: 0; + transform-origin: 100% 0; +} + +.rtl .form-floating > .form-control:focus ~ label, +.rtl .form-floating > .form-control:not(:placeholder-shown) ~ label, +.rtl .form-floating > .form-select ~ label { + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} + +/* RTL Grid System */ +@media (min-width: 576px) { + .rtl .row { + flex-direction: row-reverse; + } +} + +/* RTL Spacing Utilities */ +.rtl .ms-auto { + margin-left: 0 !important; + margin-right: auto !important; +} + +.rtl .me-auto { + margin-right: 0 !important; + margin-left: auto !important; +} + +.rtl .ps-0 { + padding-left: 0 !important; + padding-right: 0 !important; +} + +.rtl .pe-0 { + padding-right: 0 !important; + padding-left: 0 !important; +} + +/* RTL Text Alignment */ +.rtl .text-start { + text-align: right !important; +} + +.rtl .text-end { + text-align: left !important; +} + +/* RTL Float */ +.rtl .float-start { + float: right !important; +} + +.rtl .float-end { + float: left !important; +} + +/* RTL Borders */ +.rtl .border-start { + border-left: 0 !important; + border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.rtl .border-end { + border-right: 0 !important; + border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +/* RTL Shadows */ +.rtl .shadow-sm { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; +} + +/* RTL Focus */ +.rtl .focus-ring { + --bs-focus-ring-x: 0; + --bs-focus-ring-y: 0; + --bs-focus-ring-blur: 0; + --bs-focus-ring-width: 0.25rem; + --bs-focus-ring-color: rgba(13, 110, 253, 0.25); +} + +/* RTL Animations */ +.rtl .fade { + transition: opacity 0.15s linear; +} + +.rtl .collapse { + transition: height 0.35s ease; +} + +/* RTL Print Styles */ +@media print { + .rtl { + direction: rtl; + text-align: right; + } + + .rtl .d-print-none { + display: none !important; + } +} + +/* RTL Specific Components */ +.rtl .well { + text-align: right; +} + +.rtl .jumbotron { + text-align: right; +} + +.rtl .help-block { + text-align: right; +} + +/* RTL Code blocks */ +.rtl code { + direction: ltr; + text-align: left; + unicode-bidi: bidi-override; +} + +.rtl pre { + direction: ltr; + text-align: left; + unicode-bidi: bidi-override; +} + +/* RTL Numbers and dates */ +.rtl .number, +.rtl .date, +.rtl .time { + direction: ltr; + unicode-bidi: bidi-override; +} + +/* RTL Links */ +.rtl a { + text-align: right; +} + +/* RTL Images */ +.rtl img { + max-width: 100%; + height: auto; +} + +/* RTL Responsive adjustments */ +@media (max-width: 767.98px) { + .rtl .navbar-nav .nav-item { + margin-right: 0; + margin-left: 0; + } + + .rtl .dropdown-menu { + right: auto; + left: 0; + } +} \ No newline at end of file diff --git a/assets/styles/app.scss b/assets/styles/app.scss index e7964c882..aa0d51be1 100644 --- a/assets/styles/app.scss +++ b/assets/styles/app.scss @@ -2,6 +2,7 @@ @import "../../vendor/twbs/bootstrap/scss/bootstrap.scss"; @import "./bootswatch/bootswatch"; +@import "./rtl"; :root { --font-heading: 2.5rem; @@ -608,3 +609,5 @@ body#blog_search .post-metadata { padding-bottom: 0; padding-top: 0; } + + diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php index 6f4cefd4e..146ed9da3 100644 --- a/src/Twig/AppExtension.php +++ b/src/Twig/AppExtension.php @@ -34,13 +34,17 @@ final class AppExtension extends AbstractExtension public function __construct( /** @var string[] */ private readonly array $enabledLocales, + private readonly string $defaultLocale, ) { } public function getFunctions(): array { return [ - new TwigFunction('locales', $this->getLocales(...)), + new TwigFunction('locales', [$this, 'getLocales']), + new TwigFunction('is_rtl', [$this, 'isRtl']), + new TwigFunction('rtl_class', [$this, 'getRtlClass']), + new TwigFunction('rtl_dir', [$this, 'getRtlDir']), ]; } @@ -65,4 +69,30 @@ public function getLocales(): array return $this->locales; } + + /** + * Check if the given locale is RTL. + */ + public function isRtl(?string $locale = null): bool + { + $locale = $locale ?? $this->defaultLocale; + + return \in_array($locale, ['ar', 'fa', 'he', 'ur', 'ps', 'sd'], true); + } + + /** + * Get RTL class if the locale is RTL. + */ + public function getRtlClass(?string $locale = null): string + { + return $this->isRtl($locale) ? 'rtl' : ''; + } + + /** + * Get direction attribute value for RTL support. + */ + public function getRtlDir(?string $locale = null): string + { + return $this->isRtl($locale) ? 'rtl' : 'ltr'; + } } diff --git a/templates/base.html.twig b/templates/base.html.twig index dfe2cc021..11cdd4187 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -4,7 +4,7 @@ See https://symfony.com/doc/current/templates.html#template-inheritance-and-layouts #} - +
@@ -23,7 +23,7 @@ - + {% block header %} {% set _route = app.current_route %} diff --git a/templates/default/_language_selector.html.twig b/templates/default/_language_selector.html.twig index 21382360e..9d8b9ab0c 100644 --- a/templates/default/_language_selector.html.twig +++ b/templates/default/_language_selector.html.twig @@ -25,8 +25,7 @@