From d34d15242e7e5522f42df3f256efeb02c32576ea Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Mon, 20 Jan 2025 12:17:49 +0000 Subject: [PATCH] Merge pull request #22855 from overleaf/mj-ide-settings [web] Add settings modal skeleton to editor redesign GitOrigin-RevId: bc2e7f07f7ab737a67965fa615a04c8ee88b1271 --- .../web/frontend/extracted-translations.json | 7 + ...alSymbolsRoundedUnfilledPartialSlice.woff2 | Bin 2336 -> 3120 bytes .../material-symbols/unfilled-symbols.mjs | 5 + .../ide-react/components/layout/ide-page.tsx | 4 + .../features/ide-redesign/components/rail.tsx | 71 ++++++++- .../settings/settings-modal-body.tsx | 147 ++++++++++++++++++ .../components/settings/settings-modal.tsx | 31 ++++ .../stylesheets/bootstrap-5/pages/all.scss | 1 + .../bootstrap-5/pages/editor/rail.scss | 9 ++ .../bootstrap-5/pages/editor/settings.scss | 45 ++++++ services/web/locales/en.json | 7 + 11 files changed, 325 insertions(+), 2 deletions(-) create mode 100644 services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx create mode 100644 services/web/frontend/js/features/ide-redesign/components/settings/settings-modal.tsx create mode 100644 services/web/frontend/stylesheets/bootstrap-5/pages/editor/settings.scss diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 5d3bbf00df..df68da6ec1 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -441,6 +441,7 @@ "editor_disconected_click_to_reconnect": "", "editor_limit_exceeded_in_this_project": "", "editor_only_hide_pdf": "", + "editor_settings": "", "editor_theme": "", "educational_disclaimer": "", "educational_disclaimer_heading": "", @@ -554,6 +555,8 @@ "full_project_search": "", "full_width": "", "future_payments": "", + "general": "", + "general_settings": "", "generate_token": "", "generic_if_problem_continues_contact_us": "", "generic_linked_file_compile_error": "", @@ -757,6 +760,8 @@ "institutional_leavers_survey_notification": "", "integrations": "", "interested_in_cheaper_personal_plan": "", + "interface": "", + "interface_settings": "", "invalid_confirmation_code": "", "invalid_email": "", "invalid_file_name": "", @@ -1083,6 +1088,7 @@ "pay_now": "", "payment_provider_unreachable_error": "", "payment_summary": "", + "pdf": "", "pdf_compile_in_progress_error": "", "pdf_compile_rate_limit_hit": "", "pdf_compile_try_again": "", @@ -1090,6 +1096,7 @@ "pdf_only_hide_editor": "", "pdf_preview_error": "", "pdf_rendering_error": "", + "pdf_settings": "", "pdf_unavailable_for_download": "", "pdf_viewer": "", "pdf_viewer_error": "", diff --git a/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 b/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 index 6eba080dd6f7f346348e385410b131ee6b8cbf63..68ddbc999db5c6183cfc0521ce266eb957104d1c 100644 GIT binary patch literal 3120 zcmV-04A1j-Pew8T0RR9101Pky4gdfE02d$t01Mav0RR9100000000000000000000 z0000SgGvTqKT}jeRAK;uJP`;Ao+Oxj3q$|`HUcCALcXEt|9~4u^=|-+!9h;$WNXBTM0-q3Yz)9?#HeQPdfW>Au;IFy?5}e5 zD&nU(eaq|#jGS_F6S8@^UYEQPT|fI?1uGB%yTFy4WM z6ZVb=K>z_A=!YCVcJgfW<`uiQqESeCSE#jf^M-YJ6TY>V!Ud$z1PeC-55XU7o^X<( zbit^YgYUI$Lq(^D3Y46Z3bf)uVBWFRj7Jc(L6UA!AGX#x9bg-tOl@bETq-I8u8dU^pvY%uRo3mUa5jAZ>3>Cn# z5o#9H=(9~KXrbN)o0g5S-iX84v}4$Tu!Mx;H_+s`qjA%wjpGg3znLn_FN5mDqT?Kl z^~QR`GP(-`pohE1Sik_}*a?#-=egpznIlFEf!MZv*FhnX`_`@BEiAuQI}#iwZKZI< zVURCK%LGz{HA7iC;{+#?EOHd!tGF$9B0HB}O~x`du$_JHHoSZNe)*@?&&$7U`?l-b zp>L1AJ^A+k@9cjw^PKBOFJlFp*;(YZ&>NuLVLlF^+@C1-DatKIxo^xmNm6No zG-dgpKomz&he&6h7b~Nx#w4btB{h-JI&LnSpsXC*Z_+x^u}+ey-J));GQQs>2%?}< z6gi1@o@2oeebvWHf<%RG!9&^o8`qzeUcFj<9d1-Tu(N@3W2 z!E3x~CNdd%0I)Y#ags4Ro(kVhi0(?b8>5mbkd%`L(9U|n$}(zP@RdBX5datw>x6Ss zR4J(#AQ{G*;_vLPIe=n3WYu<`6zM&BzZwyW5upkZiV(RufKr@gcAAtRLam)9dWt@W zj7)%nyFclSm<5w?r&F<&H^fY}Ih}f&tB9ElCUH7_NOP4##(KXq`?bLAJ}J?A^mT1u zTWVWlQUDBY2F#;Ph4+rWOSqfJ0_G3QOeQCGTIXxa(3@WgJ*S*>IrN-%2GF0}J*MI; zV|F?f-vvqe z+Mna{xajI5y1&3rsOi7w)m0-ZkqHnQ$7WJHuk(UEqtBlzRV!F&^@kTOKq(lep~A1U zCd(EOjUhA_YiR-h;MSHgaTE6=7q+*z?a9r}%}o-MJ|D=Wndo@}J=1Iyj+V^HfruD# zLO@t^9)jcuxg?Lzr8p#EZ6+W@5b&%)nvpzFi!2WygH%~3I`RFB`N}}P1BWytyGX}i zH-0zYfuot8j~T;oWdnJS}?w|&CN|sBUw{G7S)mU1r=nu1L>raZ?BAB;mDVk z6%Zo)1D^}J9bzAjFJ{rRu)_j3McswEPMzDImrtX9<@QA^Z4c9Kla zCu6O~iX&gpEd6FL-ZxU-1W-@EO|vx5W_Uarr)tM#WYgyE+BdEn_81I%3`t3b-BP?W zH`--Sh28G6vj%9EwxLlDeG8$YrXeQY)4`&O^nG@tFf|bE&NBJot2eq?ZbymhGn#n;eU;P;NBNL zW1~#n=xDV6i_0oehaZ($zv`F1(YWt{vNDXKih_>)5plNd5R-*ld2Q z^;D)`hbSlK{Y9iWKUP^7?=q;e-y=`RsTPZ?bL0toRQ7}ME``w3a7AY(Dladpw@5NQ zFuhJ(SuU5zG9W7*5VkY(7+6VN5-ho0k|Kkr$hL zD*ZriR%B3UR&eeC{6OU&^z1KAn^KmhP4*WZ^vsuy*|rDf7g;mPf22f|I;sV3af42k zNl|r-ertN#u~wjwrpcm}W=ugzZ{UnAw8jbc%e7U(>X)mmd#cCKxuIFX+_`}v4`_|M zN)0aC zqvAVGm=@+=cxh-(Ib*Z$kVpCynHl&m7if)2SAW+sW0}lf23dNqU2l4Ndaqq?I&@dn zEq{@t%GmX#jgpuCrA?Qy=%-ko?)@JXAN;7|`TYc~u_7Rz7sD+ys(fR3_J80_YVDij zmqeGoIli{~&B+U*3*VePnK^lVNH5Y~AD*1PHndl?_xd2TMzg>gajsu*woDe3t?;W6 z2O7x&BQqM9mp2}eV1(8vD&MQAYg|&RFWadv$K=N*BostNUskE#a#GoR8jN?T3qKZg zm@Ca=*JOs{r$T~U>H?>wLyuK$_t`dk$mOv)8x=2oz1mV}UTPlyOsL_Q-Y0x#rh60J zE>8N3{lntAve%Tjpq`Rf$|ux#lS-X)ZOV%8$sJG*Ol)X7+AiqE28suA<9eF1W?iwt z+c;Y*`zgAa7uy{9ljhP{yv8$~Ennh1%X{`|PSp#X7kIOCoHEndvgfU?^Zx#v6?~NP zYB>M^-%)%1lZtUX;;j-!eP_w}ZY#$-HKEMNiv0IKzWL(>_wU?C0L;I%cn$K+*N%JV zk8l3?j{A4ew%ZciJNOIK*c%FW5E5C3o^+-~Tq8+43YI~KDok40D;$2%0Fc0!S)~JU z7>))lw>mhx3q*!{KJwiRmzNFi1!sK($09 zgcIPyKb!*zB;i~LAQ)g_MkM}0v<0%DLp!uzl{YIw4H{5~dUPTit>{Dps+7J`2!t=f;g0~~p`NT!jp zaQ=S&|L4^16Bzy%*iQJgE-{%Bc2XdaIxNj_z)(V^v;rOm*@L)i7n?aYpf0Nh0o2=} zX4ipwlny(dke%5Gn5B9_zi<#%8d`I8Vs(*d(?+O-NNAL{-QV;Jd?0LufYD$OPz;Zp>;Xd%0fgcuaClKHW#v%JPzn_cWtu?&<xw6<_kba zIaKHR1_%M*KDlr)L;xcp z%$~P!;XeDEvwYf2BG8ttJNADCpFJlVp zt(0n-ru(^H8tN-$r4sk*o~D}Wb#fx1R#T0##_k}~Vb&!Wm*aK^%?WvY!n#lzYs=6p zXUpR47df}jmqc6BfRQSWw47rx9;XmkYJBhIs5aQm|gZa;uz+-IA0(MxOm`19n>4I^e)sv#Nm1>$KGRtWq7zWV*31$hhXAQU8=|)*HGo_Q;E->p{ zaipc?LTukmlPN;+e*Gfxel(e)szqNUa_O!$n{Gd_gq;LU#AynUfrE_>7`OY!6zd_8 zYCj@6?=-`2R;T9mP4ZV5NnPSX=#ZYc%MWY_&TTC-d8HzvXr= zmy5C!`Fx&PuIs|car@hEzwN?Z*LAt9+Bv{*S(9piOJwGm!sQ?|=+K~p&xQz_&}iMD z;8{QlslwSwl@$s?kyLP**A3$%8o0#79&BiWRS##~1X!2v;@xE~vE8hjkhJoYD7XhG zCajhVlUiAKqJ{whmnnki9b{VF-cToeyQkLS8>ay?%alr6)XUYf`tGc2ZIpue@XcXw{(9&}(~ahr z-~Zr7@`Kl!Z!}e3OE%!w0s2f`rD#v-GxbXg8(h+7)^*X+P9~dnwD0MRjFZ-WSSOoF zJKA@1e#m5g$Y#D*M(@VY$ika`z2|gxoX*aySx%=^{^s1UTm3hXI&HuBKsX%^gwx@U z!+pDV?|ZnT(CTx()wNy3+lT$knBo6EYn2$;cRc>iZo^&ju9PO7-Gvvjc-=fV3*(|-Box1TT!j#}pO<-R3U zgjgy8@GY)R+hdBbd`YapDa)f^m=Z!o_;nI0;$J^PMQjC5nXCH0_b}MwJ6_jkjJ6Ug z;xM5iNJ2%FFbs9wb_Z!lH2N>L|6t+1gJt!V4V5bod(s|WG+yc_Zt`eOmG>#q<-N@* zn>-Q&x=gA?i79%N;=XisxJA-gX>$k5+0a+MsEDli|Bf6i=R-gGtwrAA1D<~JR@b&9 zx<5Nj+*_Nv=$Wv*%c#ybjk%P)SGFv}E?e87=f=qJ;%99`iUGs*o82S(bfQHMoe>b( zXweuM8QEyj7>RtY?q5TNF!Eo+IxgjPh*0_SE9RG1*+SpfV7V;+Fk!GDnalPG7B2aS zZ}s;)-Eq?Bexxy5B;IXR~+a!v&&hJ=SF1qPkU&iT$U+wjk2 z_)-7(tE3urg?h-IfPFjmFJJ0hEb1rQjPpNfGO2K<>5#aiS)cvCyE0ilT|I1e#lGzm z-m9#m-O7n-Q}OL3H&cr9-CkqFiI*B zT>kj<<8#41sIB99g3Gv#NzS~C900aZuuvR|0OW&(cww4oLs$)| zRS(;u28~dQI&yfUGZqhPcnpgVeICUUK!M*`LPTNgEss? zREsQuC4eq;do4YEg>(jZ6{=>o=niQdA=s z1t^3Br9IvXL?aiqwrZ3fmY;wou*idw7T17*5_PCNI9LD?{4g!vV8Bw0IuXo8HIj8@ zHS({Os00ZSe6R|B@JAj*%0Z-5k#Z`s2JgEMfE2Nag%kmSH(2OWu*nfnVlA4q;2nUQ z<3(5o5>n|`q8JrqGZImVLIiw3f<%}Em|`91z*dNw5b*i}_lePK>c&rM;%VN;zQe*X G0001vc~$@b diff --git a/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs b/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs index 7463addc64..54052d8773 100644 --- a/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs +++ b/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs @@ -3,9 +3,14 @@ // to update the font file with the latest icons. export default /** @type {const} */ ([ + 'code', 'description', 'forum', + 'help', 'integration_instructions', + 'picture_as_pdf', 'rate_review', 'report', + 'settings', + 'web_asset', ]) diff --git a/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx b/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx index c73c5fe1ef..84f8ba87b2 100644 --- a/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx +++ b/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx @@ -16,6 +16,9 @@ import { useFeatureFlag } from '@/shared/context/split-test-context' const MainLayoutNew = lazy( () => import('@/features/ide-redesign/components/main-layout') ) +const SettingsModalNew = lazy( + () => import('@/features/ide-redesign/components/settings/settings-modal') +) export default function IdePage() { useLayoutEventTracking() // sent event when the layout changes @@ -33,6 +36,7 @@ export default function IdePage() { {newEditor ? ( + ) : ( diff --git a/services/web/frontend/js/features/ide-redesign/components/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail.tsx index a588dfa40d..e9d21af972 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail.tsx @@ -1,9 +1,10 @@ -import { ReactElement, useCallback, useState } from 'react' +import { ReactElement, useCallback, useMemo, useState } from 'react' import { Nav, NavLink, Tab, TabContainer } from 'react-bootstrap-5' import MaterialIcon, { AvailableUnfilledIcon, } from '@/shared/components/material-icon' import { Panel } from 'react-resizable-panels' +import { useLayoutContext } from '@/shared/context/layout-context' type RailElement = { icon: AvailableUnfilledIcon @@ -11,6 +12,14 @@ type RailElement = { component: ReactElement } +type RailActionLink = { key: string; icon: AvailableUnfilledIcon; href: string } +type RailActionButton = { + key: string + icon: AvailableUnfilledIcon + action: () => void +} +type RailAction = RailActionLink | RailActionButton + const RAIL_TABS: RailElement[] = [ // NOTE: The file tree **MUST** be the first (i.e. default) tab in the list // since the file tree is responsible for opening the initial document. @@ -45,6 +54,19 @@ export const RailLayout = () => { const [selectedTab, setSelectedTab] = useState( RAIL_TABS[0]?.key ) + const { setLeftMenuShown } = useLayoutContext() + const railActions: RailAction[] = useMemo( + () => [ + { key: 'support', icon: 'help', href: '/learn' }, + { + key: 'settings', + icon: 'settings', + action: () => setLeftMenuShown(true), + }, + ], + [setLeftMenuShown] + ) + return ( { id="ide-rail-tabs" >
-
) } + +const RailActionElement = ({ action }: { action: RailAction }) => { + const icon = ( + + ) + const onActionClick = useCallback(() => { + if ('action' in action) { + action.action() + } + }, [action]) + + if ('href' in action) { + return ( + + {icon} + + ) + } else { + return ( + + ) + } +} diff --git a/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx b/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx new file mode 100644 index 0000000000..c5143a2932 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx @@ -0,0 +1,147 @@ +import MaterialIcon, { + AvailableUnfilledIcon, +} from '@/shared/components/material-icon' +import { ReactElement, useMemo, useState } from 'react' +import { + Nav, + NavLink, + TabContainer, + TabContent, + TabPane, +} from 'react-bootstrap-5' +import { useTranslation } from 'react-i18next' + +export type SettingsEntry = SettingsLink | SettingsTab + +type SettingsTab = { + icon: AvailableUnfilledIcon + key: string + component: ReactElement + title: string + subtitle: string +} + +type SettingsLink = { + key: string + icon: AvailableUnfilledIcon + href: string + title: string +} + +export const SettingsModalBody = () => { + const { t } = useTranslation() + const settingsTabs: SettingsEntry[] = useMemo( + () => [ + { + key: 'general', + title: t('general'), + subtitle: t('general_settings'), + icon: 'settings', + component:
General
, + }, + { + key: 'editor', + title: t('editor'), + subtitle: t('editor_settings'), + icon: 'code', + component:
Editor
, + }, + { + key: 'pdf', + title: t('pdf'), + subtitle: t('pdf_settings'), + icon: 'picture_as_pdf', + component:
PDF
, + }, + { + key: 'interface', + title: t('interface'), + subtitle: t('interface_settings'), + icon: 'web_asset', + component:
Interface
, + }, + { + key: 'account_settings', + title: t('account_settings'), + icon: 'settings', + href: '/user/settings', + }, + ], + [t] + ) + const [activeTab, setActiveTab] = useState( + settingsTabs[0]?.key + ) + + return ( + +
+ + + {settingsTabs + .filter(t => 'component' in t) + .map(({ key, component, subtitle }) => ( + +

{subtitle}

+
{component}
+
+ ))} +
+
+
+ ) +} + +const SettingsNavLink = ({ entry }: { entry: SettingsEntry }) => { + if ('href' in entry) { + return ( + + + {entry.title} +
+ + + ) + } else { + return ( + <> + + + {entry.title} + + + ) + } +} diff --git a/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal.tsx b/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal.tsx new file mode 100644 index 0000000000..8ee804159f --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal.tsx @@ -0,0 +1,31 @@ +import OLModal, { + OLModalBody, + OLModalHeader, + OLModalTitle, +} from '@/features/ui/components/ol/ol-modal' +import { useLayoutContext } from '@/shared/context/layout-context' +import { useTranslation } from 'react-i18next' +import { SettingsModalBody } from './settings-modal-body' + +const SettingsModal = () => { + // TODO ide-redesign-cleanup: Either rename the field, or introduce a separate + // one + const { leftMenuShown, setLeftMenuShown } = useLayoutContext() + const { t } = useTranslation() + return ( + setLeftMenuShown(false)} + size="lg" + > + + {t('settings')} + + + + + + ) +} + +export default SettingsModal diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss index 956f28777c..454f0708c5 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss @@ -8,6 +8,7 @@ @import 'editor/ide'; @import 'editor/ide-redesign'; @import 'editor/rail'; +@import 'editor/settings'; @import 'editor/toolbar'; @import 'editor/online-users'; @import 'editor/hotkeys'; diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss index 17a8cd2ba7..ba7f5b8202 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss @@ -6,6 +6,11 @@ --ide-rail-link-active-indicator-background: var(--neutral-90); } +.ide-rail-tab-button { + border: 0; + background: none; +} + .ide-rail-tab-link { border-radius: 12px; display: block; @@ -55,3 +60,7 @@ height: 100%; border: 1px solid var(--border-divider); } + +.ide-rail-tabs-nav { + height: 100%; +} diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/settings.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/settings.scss new file mode 100644 index 0000000000..75aba658e6 --- /dev/null +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/settings.scss @@ -0,0 +1,45 @@ +.ide-settings-tab-nav.nav { + width: 240px; + border-right: var(--bs-modal-header-border-width) solid + var(--bs-modal-header-border-color); + padding: var(--spacing-02); + gap: var(--spacing-02); +} + +.ide-settings-tab-content { + padding: var(--spacing-06) var(--spacing-08); + max-height: 75%; + height: 400px; +} + +.ide-settings-tab-subtitle { + font-size: var(--font-size-04); + line-height: var(--line-height-03); + padding: var(--spacing-06) var(--spacing-08); +} + +.ide-settings-tab-link { + display: flex; + align-items: flex-start; + flex-direction: row; + gap: var(--spacing-02); + color: var(--neutral-90); + padding: var(--spacing-02); + border-radius: var(--border-radius-base); + font-size: var(--font-size-02); + line-height: var(--line-height-02); + text-decoration: none; + + &:visited { + color: var(--neutral-90); + } + + &.active { + color: #fff; + background-color: var(--neutral-90); + } +} + +.ide-settings-modal-body { + padding: 0; +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index f758c24979..2931f6669c 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -576,6 +576,7 @@ "editor_disconected_click_to_reconnect": "Editor disconnected, click anywhere to reconnect.", "editor_limit_exceeded_in_this_project": "Too many editors in this project", "editor_only_hide_pdf": "Editor only <0>(hide PDF)", + "editor_settings": "Editor settings", "editor_theme": "Editor theme", "educational_disclaimer": "I confirm that users will be students or faculty using Overleaf primarily for study and teaching, and can provide evidence of this if requested.", "educational_disclaimer_heading": "Educational discount confirmation", @@ -749,6 +750,8 @@ "gallery_page_items_lowercase": "gallery items", "gallery_page_title": "Gallery - Templates, Examples and Articles written in LaTeX", "gallery_show_more_tags": "Show more", + "general": "General", + "general_settings": "General settings", "generate_token": "Generate token", "generic_if_problem_continues_contact_us": "If the problem continues please contact us", "generic_linked_file_compile_error": "This project’s output files are not available because it failed to compile. Please open the project to see the compilation error details.", @@ -995,6 +998,8 @@ "institutional_login_unknown": "Sorry, we don’t know which institution issued that email address. You can browse our list of institutions to find yours, or you can use one of the other options below.", "integrations": "Integrations", "interested_in_cheaper_personal_plan": "Would you be interested in the cheaper <0>__price__ Personal plan?", + "interface": "Interface", + "interface_settings": "Interface settings", "invalid_certificate": "Invalid certificate. Please check the certificate and try again.", "invalid_confirmation_code": "That didn’t work. Please check the code and try again.", "invalid_email": "An email address is invalid", @@ -1468,6 +1473,7 @@ "payment_method_accepted": "__paymentMethod__ accepted", "payment_provider_unreachable_error": "Sorry, there was an error talking to our payment provider. Please try again in a few moments.\nIf you are using any ad or script blocking extensions in your browser, you may need to temporarily disable them.", "payment_summary": "Payment summary", + "pdf": "PDF", "pdf_compile_in_progress_error": "A previous compile is still running. Please wait a minute and try compiling again.", "pdf_compile_rate_limit_hit": "Compile rate limit hit", "pdf_compile_try_again": "Please wait for your other compile to finish before trying again.", @@ -1475,6 +1481,7 @@ "pdf_only_hide_editor": "PDF only <0>(hide editor)", "pdf_preview_error": "There was a problem displaying the compilation results for this project.", "pdf_rendering_error": "PDF Rendering Error", + "pdf_settings": "PDF settings", "pdf_unavailable_for_download": "PDF unavailable for download", "pdf_viewer": "PDF Viewer", "pdf_viewer_error": "There was a problem displaying the PDF for this project.",