invoiceninja / invoiceninja

Invoices, Expenses and Tasks built with Laravel, Flutter and React
https://invoiceninja.com
Other
8.02k stars 2.25k forks source link

[Request] RTL support #9530

Open Zontex opened 2 months ago

Zontex commented 2 months ago

What version of Invoice Ninja are you running? ie v4.5 / v5

v5

What environment are you running? ZIP / self hosted Debian Nginx

Have you searched existing issues/requests? Yes

Additional context I've seen it's possible to add labels to each function to translate the system but how to enable RTL support? is there a built-in RTL support and if not, any chances to add?

turbo124 commented 2 months ago

@Zontex if you are referring to the PDF's and RTL support, you would need to use css helpers which would enable this.

Within the UI of the application rtl is not supported.

Zontex commented 2 months ago

@Zontex if you are referring to the PDF's and RTL support, you would need to use css helpers which would enable this.

Within the UI of the application rtl is not supported.

Hi thanks for your reply, yes this is exactly what I mean. is there any instructions available on how to do so?

turbo124 commented 2 months ago

@Zontex

these types of CSS properties are available:

p.rtl { direction: rtl; }

Zontex commented 2 months ago

@Zontex

these types of CSS properties are available:

p.rtl { direction: rtl; }

Got you, thank you so much for the quick response! any plan to make it integrated option? once changed to RTL language such as Arabic/Hebrew it will automatically change the PDF/UI view?

Zontex commented 2 months ago

I changed the invoice successfully to this CSS:

<style id="style">
    @import url($font_url);

    :root {
        --primary-color: $primary_color;
        --secondary-color: $secondary_color;
        --line-height: 1.6;
    }

    html {
        width: 210mm;
        height: 200mm;     
    }

    body {
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        font-family: $font_name, Helvetica, sans-serif;
        font-size: $font_size !important;
        zoom: 80%;
        direction: rtl; /* Added for RTL */
        text-align: right; /* Added for RTL */
    }

    table tr td, table tr, th {
        font-size: $font_size !important;
    }

    @page {
        margin-left: $global_margin;
        margin-right: $global_margin;
        margin-top: 5;
        margin-bottom: 5;
        size: $page_size $page_layout;
    }

    p {
        margin: 0;
        padding: 0;
    }

    p.rtl{
        direction: rtl;
        text-align: right;
    }

    #qr-bill {
        width: 100% !important;
        box-sizing: border-box;
        border-collapse: collapse;
        color: #000;
    }

    #qr-bill td {
        max-width: none;
    }

    .header-container {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        gap: 20px;
    }

    .company-logo-container {
        display: inline-block;
    }

    .company-logo {
        max-width: $company_logo_size;
    }

    #company-details {
        display: flex;
        flex-direction: column;
        line-height: var(--line-height);
        text-align: right; /* Added for RTL */
    }

    #company-details > p:first-child {
        color: var(--primary-color);
    }

    #company-address {
        display: flex;
        flex-direction: column;
        line-height: var(--line-height);
        text-align: right; /* Added for RTL */
    }

    .entity-label {
        margin-top: 2.5rem;
        text-transform: uppercase;
        padding-right: 1rem; /* Changed padding for RTL */
        margin-bottom: 1rem;
        font-weight: bold;
        color: var(--primary-color);
    }

    .client-and-entity-wrapper {
        padding: 1rem;
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        border-top: 1px solid #d8d8d8;
        border-bottom: 1px solid #d8d8d8;
    }

    #entity-details {
        display: flex;
        text-align: right; /* Changed text alignment for RTL */
        margin-left: 20px; /* Changed margin for RTL */
        line-height: var(--line-height) !important;
    }

    #entity-details > tr,
    #entity-details th {
        font-weight: normal;
        padding-left: 15px; /* Changed padding for RTL */
        line-height: var(--line-height) !important;
    }

    #client-details {
        display: flex;
        flex-direction: column;
        line-height: var(--line-height);
        padding-left: 30px; /* Changed padding for RTL */
    }

    #client-details > :first-child {
        font-weight: bold;
    }

    #shipping-details {
        display: $show_shipping_address;
        flex-direction: column;
        line-height: var(--line-height);
    }

    [data-ref="table"] {
        margin-top: 1rem;
        margin-bottom: 5px;
        min-width: 100%;
        table-layout: fixed;
        overflow-wrap: break-word;
    }

    .task-time-details {
        display: block;
        margin-top: 5px;
        color: grey;
    }

    [data-ref="table"] > thead {
        text-align: right; /* Changed text alignment for RTL */
    }

    [data-ref="table"] > thead > tr > th {
        font-size: 1.1rem;
        padding-bottom: 1.5rem;
        padding-right: 1rem; /* Changed padding for RTL */
    }

    [data-ref="table"] > tbody > tr > td {
        border-top: 1px solid #d8d8d8;
        border-bottom: 1px solid #d8d8d8;
        padding: 1rem 1rem;
    }

    [data-ref="table"] > tbody > tr > td:first-child {
        color: var(--primary-color);
    }

    [data-ref="table"] > thead > tr > th:last-child,
    [data-ref="table"] > tbody > tr > td:last-child {
        text-align: left; /* Changed text alignment for RTL */
    }

    [data-ref="table"] > thead > tr > th:last-child {
        padding-left: 1rem; /* Changed padding for RTL */
    }

    [data-ref="table"] > tbody > tr:nth-child(odd) {
        background-color: #f5f5f5;
    }

    #table-totals {
        margin-top: 0rem;
        display: grid;
        grid-template-columns: 2fr 1fr;
        padding-top: 0rem;
        padding-right: 1rem;
        padding-left: 1rem;
        gap: 80px;
        page-break-inside: avoid;
        overflow: visible !important;
        text-align: right; /* Added for RTL */
    }

    #table-totals .totals-table-right-side > * {
        display: grid;
        grid-template-columns: 1fr 1fr;
    }

    #table-totals>.totals-table-right-side>*> :nth-child(1) {
        text-align: right; /* Changed text alignment for RTL */
        margin-top: .75rem;
    }

    #table-totals>.totals-table-right-side>*> :nth-child(2) {
        text-align: left; /* Changed text alignment for RTL */
    }

    #table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) {
        --tw-space-y-reverse: 0;
        margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse)));
        margin-bottom: calc(.75rem * var(--tw-space-y-reverse));
    }

    #table-totals
    > *
    [data-element='product-table-balance-due-label'],
    #table-totals
    > *
    [data-element='product-table-balance-due'] {
        font-weight: bold;
    }

    #table-totals
    > *
    [data-element='product-table-balance-due'] {
        color: var(--primary-color);
    }

    #table-totals > * > :last-child {
        text-align: left; /* Changed text alignment for RTL */
        padding-left: 0.5rem; /* Changed padding for RTL */
    }

    #footer {
        margin-top: 10px;
        margin-right: 1rem; /* Changed margin for RTL */
        text-align: right; /* Added for RTL */
    }

    /** Markdown-specific styles. **/
    #product-table h3,
    #task-table h3,
    #delivery-note-table h3 {
        font-size: 1rem;
        margin-bottom: 0;
    }

    [data-ref="total_table-public_notes"] {
        margin-top: 1rem;
    }

    [data-ref="statement-totals"] {
        margin-top: 1rem;
        text-align: right;
        margin-right: .75rem;
    }

    [data-ref*=".line_total-td"] {
        white-space: nowrap;
    }

    /** .repeating-header,
    .repeating-header-space, **/
    .repeating-footer,
    .repeating-footer-space {
        height: 10px;
    }
    .repeating-header {
        position: fixed;
        top: 0;
    }
    .repeating-footer {
        position: fixed;
        bottom: 0;
    }

    [data-element='product_table-product.description-td'], td {
        min-width: 100%;
        max-width: 300px;
        overflow-wrap: break-word; 
    }

    .stamp {
        transform: rotate(12deg);
        color: #555;
        font-size: 3rem;
        font-weight: 700;
        border: 0.25rem solid #555;
        display: inline-block;
        padding: 0.25rem 1rem;
        text-transform: uppercase;
        border-radius: 1rem;
        font-family: 'Courier';
        mix-blend-mode: multiply;
        z-index: 200 !important;
        position: fixed;
        text-align: center;
        float: right;
    }

    .is-paid {
        color:  #D23;
        border: 1rem double  #D23;
        transform: rotate(-5deg);
        font-size: 6rem;
        font-family: "Open sans", Helvetica, Arial, sans-serif;
        border-radius: 0;
        padding: 0.5rem;
        opacity: 0.2;
        z-index: 200 !important;
        position: fixed;
        display: $show_paid_stamp;
    } 

    .project-header {
        font-size: 1.2em;
        margin-top: 0.1em;
        margin-bottom: 0;
        padding-bottom: 0;
        margin-left: 0;
        margin-right: 0;
        font-weight: bold;
        color: #505050;
    } 

    .pqrcode {

    }

    /** Useful snippets, uncomment to enable. **/

    /** Hide company logo **/
    /* .company-logo { display: none } */

    /* Hide company details */
    /* #company-details > * { display: none } */

    /* Hide company address */
    /* #company-address > * { display: none } */

    /* Hide public notes */
    /* [data-ref="total_table-public_notes"] { display: none } */

    /* Hide terms label */
    /* [data-ref="total_table-terms-label"] { display: none } */

    /* Hide totals table */
    /* #table-totals { display: none } */

    /* Hide totals table left side */
    /* #table-totals div:first-child > * { display: none !important } */

    /* Hide totals table right side */
    /* .totals-table-right-side { display: none } */

    /** For more info, please check our docs: https://invoiceninja.github.io **/
    /** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
</style>

But I still haven't figured out how to RTL the entire UI. when I do RTL on the body, it breaks the UI. any suggestion?

turbo124 commented 2 months ago

@Zontex

RTL is super tricky, and it is not something I am familiar with at all.

For this to be supported, the invoice designs would need to be customized to suit with the correct CSS implementation.

This would be a task best suited for a user whose language is RTL, I am happy to assist however in getting the designs updated.

Zontex commented 2 months ago

@Zontex

RTL is super tricky, and it is not something I am familiar with at all.

For this to be supported, the invoice designs would need to be customized to suit with the correct CSS implementation.

This would be a task best suited for a user whose language is RTL, I am happy to assist however in getting the designs updated.

I sent here the correct design for the invoice, confirmed it's working. multiple places need to update the RTL. I'm wondering if there is anyway within the framework to say something like IF hebrew then use RTL template, if not use normal template..?

turbo124 commented 2 months ago

@Zontex

I'm not sure this could be executed, The best solution for this would be to create a client/group setting for your RTL clients and set your RTL design as the default design for those clients.

turbo124 commented 2 months ago

@Zontex I see your design creates a complete mirror image, including reversing the columns of the invoice table. Is this the way RTL is support to work?

Zontex commented 2 months ago

@Zontex

I'm not sure this could be executed, The best solution for this would be to create a client/group setting for your RTL clients and set your RTL design as the default design for those clients.

That's a brilliant idea. Then I can leave the UI in English and serve Israeli clients with RTL portal/invoicing. will dig deeper into it. I suggest to leave this ticket open, i'll get this all working and push updates perhaps others Arabic/Hebrew needs could be served with this information.

Zontex commented 2 months ago

@Zontex I see your design creates a complete mirror image, including reversing the columns of the invoice table. Is this the way RTL is support to work?

Yes it's exactly the way RTL work. RTL is Right-to-left, everything is the opposite. If you keep English or any other non-rtl language, it will look weird, but in RTL it looks perfect.

Zontex commented 2 months ago

@turbo124 There are many localization fields missing, how can I localize Hebrew? I guess as updates went, some fields hasn't been updated. would love some guidance on where to get the latest english file so I can localize it completely to Hebrew

turbo124 commented 2 months ago

@Zontex we manage our translations here, you can join up and adjust the translations as necessary

https://transifex.com

Zontex commented 2 months ago

@Zontex

I'm not sure this could be executed, The best solution for this would be to create a client/group setting for your RTL clients and set your RTL design as the default design for those clients.

I just tested your advice it works completely well. I opened a group called it "israeli clients" and then created a template for each thing: portal, invoice etc... with RTL called it "invoice rtl" or "portal rtl" then assigned the template to that group.

Zontex commented 2 months ago

@turbo124 Quick update regarding RTL exploration:

Screenshot 2024-05-18 at 20 58 42

I managed to perfectly design the portal to RTL based on the user group using the following code:

/* Override Bootstrap for RTL */
body {
    direction: rtl;
    text-align: right;
}

.container,
.container-fluid,
.row {
    direction: rtl;
}

.col,
.col-1,
.col-2,
.col-3,
.col-4,
.col-5,
.col-6,
.col-7,
.col-8,
.col-9,
.col-10,
.col-11,
.col-12,
.col-auto,
.col-sm,
.col-sm-1,
.col-sm-2,
.col-sm-3,
.col-sm-4,
.col-sm-5,
.col-sm-6,
.col-sm-7,
.col-sm-8,
.col-sm-9,
.col-sm-10,
.col-sm-11,
.col-sm-12,
.col-sm-auto,
.col-md,
.col-md-1,
.col-md-2,
.col-md-3,
.col-md-4,
.col-md-5,
.col-md-6,
.col-md-7,
.col-md-8,
.col-md-9,
.col-md-10,
.col-md-11,
.col-md-12,
.col-md-auto,
.col-lg,
.col-lg-1,
.col-lg-2,
.col-lg-3,
.col-lg-4,
.col-lg-5,
.col-lg-6,
.col-lg-7,
.col-lg-8,
.col-lg-9,
.col-lg-10,
.col-lg-11,
.col-lg-12,
.col-lg-auto,
.col-xl,
.col-xl-1,
.col-xl-2,
.col-xl-3,
.col-xl-4,
.col-xl-5,
.col-xl-6,
.col-xl-7,
.col-xl-8,
.col-xl-9,
.col-xl-10,
.col-xl-11,
.col-xl-12,
.col-xl-auto {
    float: right;
}

.card,
.nav,
.navbar,
.modal,
.dropdown-menu,
.tooltip,
.popover {
    direction: rtl;
}

.navbar .navbar-nav {
    float: right;
}

.navbar .navbar-nav .nav-item {
    float: right;
}

.dropdown-menu {
    right: 0;
    left: auto;
}

.tooltip .arrow::before {
    border-right-color: #000;
    border-left-color: transparent;
}

.popover .arrow::before {
    border-right-color: #fff;
    border-left-color: transparent;
}

/* Additional Bootstrap component adjustments */
.form-control {
    text-align: right;
}

.input-group .input-group-text,
.input-group .form-control {
    direction: rtl;
    text-align: right;
}

.input-group-prepend,
.input-group-append {
    float: right;
}

.input-group-prepend .input-group-text,
.input-group-append .input-group-text {
    direction: rtl;
}

.modal-dialog {
    float: right;
}

.breadcrumb {
    direction: rtl;
}

.pagination {
    direction: rtl;
}

.pagination .page-link {
    direction: rtl;
}

/* Additional custom styles */
.custom-rtl {
    direction: rtl;
    text-align: right;
}

/* Ensure all custom classes with RTL are properly aligned */
.custom-rtl .row,
.custom-rtl .col,
.custom-rtl .container,
.custom-rtl .container-fluid,
.custom-rtl .card,
.custom-rtl .nav,
.custom-rtl .navbar,
.custom-rtl .modal,
.custom-rtl .dropdown-menu,
.custom-rtl .tooltip,
.custom-rtl .popover,
.custom-rtl .breadcrumb,
.custom-rtl .pagination,
.custom-rtl .form-control,
.custom-rtl .input-group,
.custom-rtl .input-group-prepend,
.custom-rtl .input-group-append,
.custom-rtl .input-group-text {
    direction: rtl;
    text-align: right;
    float: right;
}

.custom-rtl .navbar .navbar-nav {
    float: right;
}

.custom-rtl .navbar .navbar-nav .nav-item {
    float: right;
}

.custom-rtl .dropdown-menu {
    right: 0;
    left: auto;
}

.custom-rtl .tooltip .arrow::before {
    border-right-color: #000;
    border-left-color: transparent;
}

.custom-rtl .popover .arrow::before {
    border-right-color: #fff;
    border-left-color: transparent;
}

Now the last issue that seems I can't resolve is the main dashboard. if I create an account to login to Invoice ninja with RTL language by default, I dont see a place where I can inject custom CSS code into the dashboard object? in the portal by injecting the code I could fix all the portal dashboard, any chance to put a priority into adding css/javascript custom code injection on the dashboard based on a specific user / user group?

turbo124 commented 2 months ago

The front end implementation would need to have the react code modified.

@beganovich is there an easy win to support RTL with the react UI?

hillelcoren commented 2 months ago

Note: the desktop/mobile app supports RTL for Arabic, I assume it can be enabled for other languages as well.

image

Zontex commented 2 months ago

Note: the desktop/mobile app supports RTL for Arabic, I assume it can be enabled for other languages as well.

image

Screenshot 2024-05-19 at 14 23 57

For me the desktop app still shows LTR (same as web), didn't try the mobile yet. I changed the Arabic in the desktop app also shows LTR, how does it show RTL for you? I'm using OSX desktop app

hillelcoren commented 2 months ago

That's the web app, here are links to the desktop app.

https://invoiceninja.com/apps

Zontex commented 2 months ago

No it's the Desktop APP for OSX, I just took a screenshot from inside

On Sun, May 19, 2024 at 3:01 PM Hillel Coren @.***> wrote:

That's the web app, here are links to the desktop app.

https://invoiceninja.com/apps

— Reply to this email directly, view it on GitHub https://github.com/invoiceninja/invoiceninja/issues/9530#issuecomment-2119210507, or unsubscribe https://github.com/notifications/unsubscribe-auth/AETSJRN3LVEORW7OONE4PKDZDCIBDAVCNFSM6AAAAABH4B23X6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMJZGIYTANJQG4 . You are receiving this because you were mentioned.Message ID: @.***>

-- ~ Roni


Roni Gorodetsky CN: +86 (136) 8688 2152 IL: +972 (054) 9736 866 微信 (Wechat ID): rongo1706 Whatsapp: +972 (054) 9736 866

hillelcoren commented 2 months ago

This is the macOS app, the screenshot you shared is the React web app.

https://apps.apple.com/us/app/invoice-ninja/id1503970375