From ab67831e4bbeff364be36dd98eaeb6630f041cdf Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Sat, 5 Sep 2026 18:33:35 -0700 Subject: [PATCH] feat: add cover and contain picture fitting (#3461) * feat: add picture fit options * fix: harden picture fit regressions * fix: address picture fit review findings --- apps/server/src/openapi/generator.test.ts | 10 + apps/web/locales/af-ZA.po | 16 ++ apps/web/locales/am-ET.po | 16 ++ apps/web/locales/ar-SA.po | 16 ++ apps/web/locales/az-AZ.po | 16 ++ apps/web/locales/bg-BG.po | 16 ++ apps/web/locales/bn-BD.po | 16 ++ apps/web/locales/ca-ES.po | 16 ++ apps/web/locales/cs-CZ.po | 16 ++ apps/web/locales/da-DK.po | 16 ++ apps/web/locales/de-DE.po | 16 ++ apps/web/locales/el-GR.po | 16 ++ apps/web/locales/en-GB.po | 16 ++ apps/web/locales/en-US.po | 16 ++ apps/web/locales/es-ES.po | 16 ++ apps/web/locales/fa-IR.po | 16 ++ apps/web/locales/fi-FI.po | 16 ++ apps/web/locales/fr-FR.po | 16 ++ apps/web/locales/he-IL.po | 16 ++ apps/web/locales/hi-IN.po | 16 ++ apps/web/locales/hu-HU.po | 16 ++ apps/web/locales/id-ID.po | 16 ++ apps/web/locales/it-IT.po | 16 ++ apps/web/locales/ja-JP.po | 16 ++ apps/web/locales/km-KH.po | 16 ++ apps/web/locales/kn-IN.po | 16 ++ apps/web/locales/ko-KR.po | 16 ++ apps/web/locales/lt-LT.po | 16 ++ apps/web/locales/lv-LV.po | 16 ++ apps/web/locales/ml-IN.po | 16 ++ apps/web/locales/mr-IN.po | 16 ++ apps/web/locales/ms-MY.po | 16 ++ apps/web/locales/ne-NP.po | 16 ++ apps/web/locales/nl-NL.po | 16 ++ apps/web/locales/no-NO.po | 16 ++ apps/web/locales/or-IN.po | 16 ++ apps/web/locales/pl-PL.po | 16 ++ apps/web/locales/pt-BR.po | 16 ++ apps/web/locales/pt-PT.po | 16 ++ apps/web/locales/ro-RO.po | 16 ++ apps/web/locales/ru-RU.po | 16 ++ apps/web/locales/sk-SK.po | 16 ++ apps/web/locales/sl-SI.po | 16 ++ apps/web/locales/sq-AL.po | 16 ++ apps/web/locales/sr-SP.po | 16 ++ apps/web/locales/sv-SE.po | 16 ++ apps/web/locales/ta-IN.po | 16 ++ apps/web/locales/te-IN.po | 16 ++ apps/web/locales/th-TH.po | 16 ++ apps/web/locales/tr-TR.po | 16 ++ apps/web/locales/uk-UA.po | 16 ++ apps/web/locales/uz-UZ.po | 16 ++ apps/web/locales/vi-VN.po | 16 ++ apps/web/locales/zh-CN.po | 16 ++ apps/web/locales/zh-TW.po | 16 ++ apps/web/locales/zu-ZA.po | 16 ++ .../left/sections/field-labels.test.tsx | 71 ++++- .../-sidebar/left/sections/picture.tsx | 63 ++++- docs/guides/json-resume-schema.mdx | 9 + .../src/reactive-resume-v4-json.broad.test.ts | 1 + .../import/src/reactive-resume-v4-json.tsx | 1 + .../templates/shared/base-template-styles.ts | 2 +- .../shared/picture-fit.integration.test.tsx | 254 ++++++++++++++++++ .../pdf/src/templates/shared/picture.test.ts | 1 + packages/schema/src/resume/data.test.ts | 14 + packages/schema/src/resume/data.ts | 4 + packages/schema/src/resume/default.test.ts | 4 + packages/schema/src/resume/default.ts | 1 + .../schema/src/resume/json-schema.test.ts | 10 + packages/schema/src/resume/json-schema.ts | 8 +- packages/schema/src/resume/sample.ts | 1 + skills/resume-builder/references/schema.md | 1 + tests/e2e/specs/picture-rendering.spec.ts | 237 ++++++++++++++++ 73 files changed, 1564 insertions(+), 8 deletions(-) create mode 100644 packages/pdf/src/templates/shared/picture-fit.integration.test.tsx create mode 100644 tests/e2e/specs/picture-rendering.spec.ts diff --git a/apps/server/src/openapi/generator.test.ts b/apps/server/src/openapi/generator.test.ts index a4cb26dd8..967e9c345 100644 --- a/apps/server/src/openapi/generator.test.ts +++ b/apps/server/src/openapi/generator.test.ts @@ -121,6 +121,16 @@ describe("generateOpenApiSpec", () => { }); }); + it("accepts legacy input with omitted picture fit in the published request schema", async () => { + const spec = (await generateSpec()) as GeneratedSpecView; + + expect(spec.components?.schemas?.ResumeData).toMatchObject({ + properties: { + picture: { required: expect.not.arrayContaining(["fit"]) }, + }, + }); + }); + it("publishes the custom-section type and item correlation", async () => { const spec = (await generateSpec()) as GeneratedSpecView; const schema = z.fromJSONSchema(spec.components?.schemas?.ResumeData as Parameters[0]); diff --git a/apps/web/locales/af-ZA.po b/apps/web/locales/af-ZA.po index ff0138a61..6664c9a36 100644 --- a/apps/web/locales/af-ZA.po +++ b/apps/web/locales/af-ZA.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" en die volledige tydlyn daarvan sal permanent uitgevee word. Dit kan nie ongedaan gemaak word nie." @@ -1214,6 +1222,10 @@ msgstr "Inhoud" msgid "Content is laid out in a grid." msgstr "Inhoud is in 'n rooster uitgelê." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Fins" msgid "Fireworks" msgstr "Vuurwerke" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Pas om te sien" diff --git a/apps/web/locales/am-ET.po b/apps/web/locales/am-ET.po index 6ebb0584b..af330033f 100644 --- a/apps/web/locales/am-ET.po +++ b/apps/web/locales/am-ET.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" እና ሙሉ የጊዜ መስመሩ በቋሚነት ይሰረዛሉ። ይህ ሊቀለበስ አይችልም።" @@ -1214,6 +1222,10 @@ msgstr "ይዘት" msgid "Content is laid out in a grid." msgstr "ይዘቱ በግሪድ ውስጥ ተቀምጧል።" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "ፊኒሽ" msgid "Fireworks" msgstr "ርችቶች" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "ለእይታ ተስማሚ" diff --git a/apps/web/locales/ar-SA.po b/apps/web/locales/ar-SA.po index 1dd78dcdf..785cf4c35 100644 --- a/apps/web/locales/ar-SA.po +++ b/apps/web/locales/ar-SA.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "سيتم حذف \"{0} · {1}\" وسجله الزمني الكامل نهائيًا. لا يمكن التراجع عن ذلك." @@ -1214,6 +1222,10 @@ msgstr "المحتوى" msgid "Content is laid out in a grid." msgstr "المحتوى منظّم في شبكة." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "الفنلندية" msgid "Fireworks" msgstr "ألعاب نارية" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "مناسب للعرض" diff --git a/apps/web/locales/az-AZ.po b/apps/web/locales/az-AZ.po index ec1f2bda9..c144999d2 100644 --- a/apps/web/locales/az-AZ.po +++ b/apps/web/locales/az-AZ.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" və onun tam zaman xətti həmişəlik silinəcək. Bu əməliyyatı geri qaytarmaq mümkün deyil." @@ -1214,6 +1222,10 @@ msgstr "Məzmun" msgid "Content is laid out in a grid." msgstr "Məzmun cədvəl (grid) şəklində düzülüb." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Fin" msgid "Fireworks" msgstr "Atəşfəşanlıq" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Görüntü üçün uyğunlaşdırın" diff --git a/apps/web/locales/bg-BG.po b/apps/web/locales/bg-BG.po index f6eddb01f..8348237d3 100644 --- a/apps/web/locales/bg-BG.po +++ b/apps/web/locales/bg-BG.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" и цялата му хронология ще бъдат изтрити завинаги. Това не може да бъде отменено." @@ -1214,6 +1222,10 @@ msgstr "Съдържание" msgid "Content is laid out in a grid." msgstr "Съдържанието е оформено в мрежа." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Фински" msgid "Fireworks" msgstr "Фойерверки" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Побиране в изглед" diff --git a/apps/web/locales/bn-BD.po b/apps/web/locales/bn-BD.po index 59af04efb..b0b14fa69 100644 --- a/apps/web/locales/bn-BD.po +++ b/apps/web/locales/bn-BD.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" এবং এর সম্পূর্ণ টাইমলাইন স্থায়ীভাবে মুছে ফেলা হবে। এটি পূর্বাবস্থায় ফেরানো যাবে না।" @@ -1214,6 +1222,10 @@ msgstr "বিষয়বস্তু" msgid "Content is laid out in a grid." msgstr "কনটেন্ট একটি গ্রিডে সাজানো আছে।" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "ফিনিশ" msgid "Fireworks" msgstr "আতশবাজি" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "দেখার জন্য উপযুক্ত" diff --git a/apps/web/locales/ca-ES.po b/apps/web/locales/ca-ES.po index 5a8511627..bcbcf3209 100644 --- a/apps/web/locales/ca-ES.po +++ b/apps/web/locales/ca-ES.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" i tota la seva cronologia se suprimiran permanentment. Això no es pot desfer." @@ -1214,6 +1222,10 @@ msgstr "Contingut" msgid "Content is laid out in a grid." msgstr "El contingut està disposat en una graella." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finès" msgid "Fireworks" msgstr "Focs artificials" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Ajustar a la vista" diff --git a/apps/web/locales/cs-CZ.po b/apps/web/locales/cs-CZ.po index 25a7d9d02..9760a5026 100644 --- a/apps/web/locales/cs-CZ.po +++ b/apps/web/locales/cs-CZ.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" a celá jeho časová osa budou trvale smazány. Tuto akci nelze vrátit zpět." @@ -1214,6 +1222,10 @@ msgstr "Obsah" msgid "Content is laid out in a grid." msgstr "Obsah je uspořádán do mřížky." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finština" msgid "Fireworks" msgstr "Ohňostroj" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Přizpůsobit zobrazení" diff --git a/apps/web/locales/da-DK.po b/apps/web/locales/da-DK.po index a382810e3..903860402 100644 --- a/apps/web/locales/da-DK.po +++ b/apps/web/locales/da-DK.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" og hele dens tidslinje slettes permanent. Dette kan ikke fortrydes." @@ -1214,6 +1222,10 @@ msgstr "Indhold" msgid "Content is laid out in a grid." msgstr "Indhold er opsat i et gitter." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finsk" msgid "Fireworks" msgstr "Fyrværkeri" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Tilpas til visning" diff --git a/apps/web/locales/de-DE.po b/apps/web/locales/de-DE.po index d4fe84564..fdff6870e 100644 --- a/apps/web/locales/de-DE.po +++ b/apps/web/locales/de-DE.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" und der gesamte Verlauf werden dauerhaft gelöscht. Dies kann nicht rückgängig gemacht werden." @@ -1214,6 +1222,10 @@ msgstr "Inhalt" msgid "Content is laid out in a grid." msgstr "Der Inhalt ist als Raster angeordnet." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finnisch" msgid "Fireworks" msgstr "Feuerwerk" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Passend zur Ansicht" diff --git a/apps/web/locales/el-GR.po b/apps/web/locales/el-GR.po index fb1a4252a..defbbb616 100644 --- a/apps/web/locales/el-GR.po +++ b/apps/web/locales/el-GR.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" και ολόκληρο το χρονολόγιό του θα διαγραφούν οριστικά. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί." @@ -1214,6 +1222,10 @@ msgstr "Περιεχόμενο" msgid "Content is laid out in a grid." msgstr "Το περιεχόμενο είναι διατεταγμένο σε πλέγμα." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Φινλανδικά" msgid "Fireworks" msgstr "Πυροτεχνήματα" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Κατάλληλο για προβολή" diff --git a/apps/web/locales/en-GB.po b/apps/web/locales/en-GB.po index e4dfb740b..d6f7c8c75 100644 --- a/apps/web/locales/en-GB.po +++ b/apps/web/locales/en-GB.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." @@ -1214,6 +1222,10 @@ msgstr "Content" msgid "Content is laid out in a grid." msgstr "Content is laid out in a grid." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finnish" msgid "Fireworks" msgstr "Fireworks" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Fit to view" diff --git a/apps/web/locales/en-US.po b/apps/web/locales/en-US.po index 19aaebc51..6be1a104c 100644 --- a/apps/web/locales/en-US.po +++ b/apps/web/locales/en-US.po @@ -1260,6 +1260,10 @@ msgstr "Content" msgid "Content is laid out in a grid." msgstr "Content is laid out in a grid." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "Contain" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -1458,6 +1462,14 @@ msgstr "Count by source" msgid "Counted separately from the parse score. Coverage does not predict anything." msgstr "Counted separately from the parse score. Coverage does not predict anything." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "Cover" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." + #: src/features/applications/components/application-detail-sheet.tsx #: src/features/applications/components/application-form-sheet.tsx #: src/features/cover-letters/library.tsx @@ -2505,6 +2517,10 @@ msgstr "Finnish" msgid "Fireworks" msgstr "Fireworks" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "Fit" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Fit to view" diff --git a/apps/web/locales/es-ES.po b/apps/web/locales/es-ES.po index 19ee92f5f..49b4d5451 100644 --- a/apps/web/locales/es-ES.po +++ b/apps/web/locales/es-ES.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" y todo su historial se eliminarán permanentemente. Esta acción no se puede deshacer." @@ -1214,6 +1222,10 @@ msgstr "Contenido" msgid "Content is laid out in a grid." msgstr "El contenido está organizado en una cuadrícula." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finés" msgid "Fireworks" msgstr "Fuegos artificiales" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Apto para visualización" diff --git a/apps/web/locales/fa-IR.po b/apps/web/locales/fa-IR.po index 7d531aa64..e22f0c7c9 100644 --- a/apps/web/locales/fa-IR.po +++ b/apps/web/locales/fa-IR.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" و کل تاریخچه آن برای همیشه حذف می‌شوند. این کار قابل بازگشت نیست." @@ -1214,6 +1222,10 @@ msgstr "محتوا" msgid "Content is laid out in a grid." msgstr "محتوا به‌صورت شبکه‌ای چیده شده است." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "فنلاندی" msgid "Fireworks" msgstr "آتش بازی" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "مناسب برای مشاهده" diff --git a/apps/web/locales/fi-FI.po b/apps/web/locales/fi-FI.po index 1258eee9e..d18abc033 100644 --- a/apps/web/locales/fi-FI.po +++ b/apps/web/locales/fi-FI.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" ja sen koko aikajana poistetaan pysyvästi. Tätä ei voi kumota." @@ -1214,6 +1222,10 @@ msgstr "Sisältö" msgid "Content is laid out in a grid." msgstr "Sisältö on aseteltu ruudukkoon." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "suomi" msgid "Fireworks" msgstr "Ilotulitus" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Sovita näkymään" diff --git a/apps/web/locales/fr-FR.po b/apps/web/locales/fr-FR.po index 42c93b8c7..33f27d08f 100644 --- a/apps/web/locales/fr-FR.po +++ b/apps/web/locales/fr-FR.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" et tout son historique seront supprimés définitivement. Cette action est irréversible." @@ -1214,6 +1222,10 @@ msgstr "Contenu" msgid "Content is laid out in a grid." msgstr "Le contenu est disposé en grille." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finnois" msgid "Fireworks" msgstr "Feux d'artifice" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Ajuster à la vue" diff --git a/apps/web/locales/he-IL.po b/apps/web/locales/he-IL.po index 5bbd72c51..f68f526f7 100644 --- a/apps/web/locales/he-IL.po +++ b/apps/web/locales/he-IL.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" וכל ציר הזמן שלו יימחקו לצמיתות. אי אפשר לבטל פעולה זו." @@ -1214,6 +1222,10 @@ msgstr "תוכן" msgid "Content is laid out in a grid." msgstr "התוכן מסודר ברשת." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "פינית" msgid "Fireworks" msgstr "זיקוקים" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "התאמה לתצוגה" diff --git a/apps/web/locales/hi-IN.po b/apps/web/locales/hi-IN.po index a21f4f655..3157bab5a 100644 --- a/apps/web/locales/hi-IN.po +++ b/apps/web/locales/hi-IN.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" और इसकी पूरी टाइमलाइन स्थायी रूप से हटा दी जाएगी। इसे पूर्ववत नहीं किया जा सकता." @@ -1214,6 +1222,10 @@ msgstr "सामग्री" msgid "Content is laid out in a grid." msgstr "सामग्री ग्रिड में व्यवस्थित है।" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "फिनिश" msgid "Fireworks" msgstr "आतिशबाजी" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "देखने के लिए उपयुक्त" diff --git a/apps/web/locales/hu-HU.po b/apps/web/locales/hu-HU.po index 0ba484a90..ccdda5b95 100644 --- a/apps/web/locales/hu-HU.po +++ b/apps/web/locales/hu-HU.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" és a teljes idővonala véglegesen törlődik. Ez nem vonható vissza." @@ -1214,6 +1222,10 @@ msgstr "Tartalom" msgid "Content is laid out in a grid." msgstr "A tartalom rácsszerkezetben van elrendezve." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "finn" msgid "Fireworks" msgstr "Tűzijáték" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Nézethez igazítva" diff --git a/apps/web/locales/id-ID.po b/apps/web/locales/id-ID.po index 5bc239c17..0507bff73 100644 --- a/apps/web/locales/id-ID.po +++ b/apps/web/locales/id-ID.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" dan seluruh linimasanya akan dihapus permanen. Ini tidak dapat dibatalkan." @@ -1214,6 +1222,10 @@ msgstr "Konten" msgid "Content is laid out in a grid." msgstr "Konten ditata dalam bentuk kisi." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finlandia" msgid "Fireworks" msgstr "Kembang api" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Sesuai untuk dilihat" diff --git a/apps/web/locales/it-IT.po b/apps/web/locales/it-IT.po index 663dde948..81a8df200 100644 --- a/apps/web/locales/it-IT.po +++ b/apps/web/locales/it-IT.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" e tutta la sua cronologia verranno eliminati definitivamente. Questa azione non può essere annullata." @@ -1214,6 +1222,10 @@ msgstr "Contenuto" msgid "Content is laid out in a grid." msgstr "Il contenuto è impaginato in una griglia." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finlandese" msgid "Fireworks" msgstr "fuochi d'artificio" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Adatto alla visione" diff --git a/apps/web/locales/ja-JP.po b/apps/web/locales/ja-JP.po index fa46f1f23..cc63642c5 100644 --- a/apps/web/locales/ja-JP.po +++ b/apps/web/locales/ja-JP.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" とその全タイムラインは完全に削除されます。この操作は元に戻せません。" @@ -1214,6 +1222,10 @@ msgstr "コンテンツ" msgid "Content is laid out in a grid." msgstr "コンテンツがグリッドレイアウトになっています。" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "フィンランド語" msgid "Fireworks" msgstr "花火" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "見るのにふさわしい" diff --git a/apps/web/locales/km-KH.po b/apps/web/locales/km-KH.po index 77227f3f9..697e06dce 100644 --- a/apps/web/locales/km-KH.po +++ b/apps/web/locales/km-KH.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" និងប្រវត្តិពេលវេលាទាំងមូលរបស់វានឹងត្រូវបានលុបជាអចិន្ត្រៃយ៍។ មិនអាចត្រឡប់វិញបានទេ។" @@ -1214,6 +1222,10 @@ msgstr "មាតិកា" msgid "Content is laid out in a grid." msgstr "មាតិកាត្រូវបានរៀបចំតាមទម្រង់ក្រឡាចត្រង្គ។" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finnish" msgid "Fireworks" msgstr "កាំជ្រួច" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "សមនឹងមើល" diff --git a/apps/web/locales/kn-IN.po b/apps/web/locales/kn-IN.po index e7b0fe2e7..0243d2e42 100644 --- a/apps/web/locales/kn-IN.po +++ b/apps/web/locales/kn-IN.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" ಮತ್ತು ಅದರ ಸಂಪೂರ್ಣ ಕಾಲರೇಖೆಯನ್ನು ಶಾಶ್ವತವಾಗಿ ಅಳಿಸಲಾಗುತ್ತದೆ. ಇದನ್ನು ಹಿಂತಿರುಗಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ." @@ -1214,6 +1222,10 @@ msgstr "ವಿಷಯ" msgid "Content is laid out in a grid." msgstr "ವಿಷಯವನ್ನು ಗ್ರಿಡ್‌ನಲ್ಲಿ ಜೋಡಿಸಲಾಗಿದೆ." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "ಫಿನ್ನಿಷ್" msgid "Fireworks" msgstr "ಪಟಾಕಿಗಳು" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "ವೀಕ್ಷಿಸಲು ಹೊಂದಿಕೊಳ್ಳಿ" diff --git a/apps/web/locales/ko-KR.po b/apps/web/locales/ko-KR.po index 1bfe5f044..7bbf4bd57 100644 --- a/apps/web/locales/ko-KR.po +++ b/apps/web/locales/ko-KR.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" 및 전체 타임라인이 영구적으로 삭제됩니다. 이 작업은 되돌릴 수 없습니다." @@ -1214,6 +1222,10 @@ msgstr "내용" msgid "Content is laid out in a grid." msgstr "내용이 격자 형태로 배치되어 있습니다." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "핀란드어" msgid "Fireworks" msgstr "불꽃" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "보기에 적합함" diff --git a/apps/web/locales/lt-LT.po b/apps/web/locales/lt-LT.po index c5c878edf..339e862cc 100644 --- a/apps/web/locales/lt-LT.po +++ b/apps/web/locales/lt-LT.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" ir visa jos laiko juosta bus visam laikui ištrinta. To negalima atšaukti." @@ -1214,6 +1222,10 @@ msgstr "Turinys" msgid "Content is laid out in a grid." msgstr "Turinys sumaketuotas tinkleliu." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Suomių" msgid "Fireworks" msgstr "Fejerverkai" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Pritaikyti peržiūrai" diff --git a/apps/web/locales/lv-LV.po b/apps/web/locales/lv-LV.po index 1362fb21b..1d28952e5 100644 --- a/apps/web/locales/lv-LV.po +++ b/apps/web/locales/lv-LV.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" un visa tās laika skala tiks neatgriezeniski dzēsta. To nevar atsaukt." @@ -1214,6 +1222,10 @@ msgstr "Saturs" msgid "Content is laid out in a grid." msgstr "Saturs ir izkārtots režģī." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Somu" msgid "Fireworks" msgstr "Uguņošana" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Piemērots skatam" diff --git a/apps/web/locales/ml-IN.po b/apps/web/locales/ml-IN.po index 45e55f786..0b9fe43af 100644 --- a/apps/web/locales/ml-IN.po +++ b/apps/web/locales/ml-IN.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" എന്നതും അതിന്റെ പൂർണ്ണ ടൈംലൈനും ശാശ്വതമായി ഇല്ലാതാക്കും. ഇത് പഴയപടിയാക്കാൻ കഴിയില്ല." @@ -1214,6 +1222,10 @@ msgstr "ഉള്ളടക്കം" msgid "Content is laid out in a grid." msgstr "ഉള്ളടക്കം ഒരു ഗ്രിഡിലായി ക്രമീകരിച്ചിരിക്കുന്നു." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "ഫിന്നിഷ്" msgid "Fireworks" msgstr "വെടിക്കെട്ട്" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "കാണാൻ അനുയോജ്യം" diff --git a/apps/web/locales/mr-IN.po b/apps/web/locales/mr-IN.po index 61a7ebe33..669c3a26e 100644 --- a/apps/web/locales/mr-IN.po +++ b/apps/web/locales/mr-IN.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" आणि त्याची पूर्ण टाइमलाइन कायमची हटवली जाईल. हे पूर्ववत करता येणार नाही." @@ -1214,6 +1222,10 @@ msgstr "सामग्री" msgid "Content is laid out in a grid." msgstr "सामग्री ग्रिडमध्ये मांडलेली आहे." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "फिन्निश" msgid "Fireworks" msgstr "फटाके" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "पाहण्यासाठी योग्य" diff --git a/apps/web/locales/ms-MY.po b/apps/web/locales/ms-MY.po index 473085608..b9e54472a 100644 --- a/apps/web/locales/ms-MY.po +++ b/apps/web/locales/ms-MY.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" dan garis masa penuhnya akan dipadamkan secara kekal. Tindakan ini tidak boleh dibuat asal." @@ -1214,6 +1222,10 @@ msgstr "Kandungan" msgid "Content is laid out in a grid." msgstr "Kandungan disusun dalam grid." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finland" msgid "Fireworks" msgstr "Bunga api" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Sesuai untuk dilihat" diff --git a/apps/web/locales/ne-NP.po b/apps/web/locales/ne-NP.po index 1e30cadc1..7d7bfbfc6 100644 --- a/apps/web/locales/ne-NP.po +++ b/apps/web/locales/ne-NP.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" र यसको पूरा टाइमलाइन स्थायी रूपमा मेटिनेछ। यो पूर्ववत गर्न सकिँदैन।" @@ -1214,6 +1222,10 @@ msgstr "सामग्री" msgid "Content is laid out in a grid." msgstr "सामग्री ग्रिडमा सजाइएको छ।" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "फिनिस" msgid "Fireworks" msgstr "आतिशबाजी" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "हेर्नको लागि उपयुक्त" diff --git a/apps/web/locales/nl-NL.po b/apps/web/locales/nl-NL.po index aa13b8cab..c3541a605 100644 --- a/apps/web/locales/nl-NL.po +++ b/apps/web/locales/nl-NL.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" en de volledige tijdlijn worden permanent verwijderd. Dit kan niet ongedaan worden gemaakt." @@ -1214,6 +1222,10 @@ msgstr "Inhoud" msgid "Content is laid out in a grid." msgstr "Inhoud is opgemaakt in een raster." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Fins" msgid "Fireworks" msgstr "Vuurwerk" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Geschikt om te bekijken" diff --git a/apps/web/locales/no-NO.po b/apps/web/locales/no-NO.po index 7a273666e..b91718da1 100644 --- a/apps/web/locales/no-NO.po +++ b/apps/web/locales/no-NO.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" og hele tidslinjen slettes permanent. Dette kan ikke angres." @@ -1214,6 +1222,10 @@ msgstr "Innhold" msgid "Content is laid out in a grid." msgstr "Innholdet er lagt ut i et rutenett." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finsk" msgid "Fireworks" msgstr "Fyrverkeri" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Tilpasset til visning" diff --git a/apps/web/locales/or-IN.po b/apps/web/locales/or-IN.po index 59f95903c..fb54d7dab 100644 --- a/apps/web/locales/or-IN.po +++ b/apps/web/locales/or-IN.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" ଏବଂ ଏହାର ସମ୍ପୂର୍ଣ୍ଣ ସମୟରେଖା ସ୍ଥାୟୀଭାବେ ହଟାଯିବ। ଏହା ପୁନଃଫେରାଇ ହେବ ନାହିଁ।" @@ -1214,6 +1222,10 @@ msgstr "ବିଷୟବସ୍ତୁ" msgid "Content is laid out in a grid." msgstr "ବିଷୟବସ୍ତୁ ଏକ ଗ୍ରିଡ୍‌ରେ ସଜ୍ଜିତ ଅଛି।" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "ଫିନିଶ୍" msgid "Fireworks" msgstr "ଆତସବାଜି" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "ଦେଖିବା ପାଇଁ ଫିଟ୍" diff --git a/apps/web/locales/pl-PL.po b/apps/web/locales/pl-PL.po index 3e667f933..56ae5c976 100644 --- a/apps/web/locales/pl-PL.po +++ b/apps/web/locales/pl-PL.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" i pełna oś czasu zostaną trwale usunięte. Tej operacji nie można cofnąć." @@ -1214,6 +1222,10 @@ msgstr "Treść" msgid "Content is laid out in a grid." msgstr "Treść jest ułożona w siatce." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Fiński" msgid "Fireworks" msgstr "Fajerwerki" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Dopasuj do widoku" diff --git a/apps/web/locales/pt-BR.po b/apps/web/locales/pt-BR.po index f10d560ec..a8bf0fd05 100644 --- a/apps/web/locales/pt-BR.po +++ b/apps/web/locales/pt-BR.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" e toda a sua linha do tempo serão excluídos permanentemente. Isso não pode ser desfeito." @@ -1214,6 +1222,10 @@ msgstr "Conteúdo" msgid "Content is laid out in a grid." msgstr "O conteúdo está organizado em uma grade." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finlandês" msgid "Fireworks" msgstr "Fogos de artifício" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Adequado para visualização" diff --git a/apps/web/locales/pt-PT.po b/apps/web/locales/pt-PT.po index c3d908dce..054474ae9 100644 --- a/apps/web/locales/pt-PT.po +++ b/apps/web/locales/pt-PT.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" e a sua cronologia completa serão eliminados permanentemente. Isto não pode ser anulado." @@ -1214,6 +1222,10 @@ msgstr "Conteúdo" msgid "Content is laid out in a grid." msgstr "O conteúdo está organizado numa grelha." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finlandês" msgid "Fireworks" msgstr "Fogos de artifício" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Adequado para visualização" diff --git a/apps/web/locales/ro-RO.po b/apps/web/locales/ro-RO.po index c1103466d..5adb25379 100644 --- a/apps/web/locales/ro-RO.po +++ b/apps/web/locales/ro-RO.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" și istoricul său complet vor fi șterse definitiv. Această acțiune nu poate fi anulată." @@ -1214,6 +1222,10 @@ msgstr "Conținut" msgid "Content is laid out in a grid." msgstr "Conținutul este dispus într-o grilă." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finlandeză" msgid "Fireworks" msgstr "Focuri de artificii" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Potrivire pentru vizualizare" diff --git a/apps/web/locales/ru-RU.po b/apps/web/locales/ru-RU.po index a94ea8282..c049eb177 100644 --- a/apps/web/locales/ru-RU.po +++ b/apps/web/locales/ru-RU.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" и вся хронология будут безвозвратно удалены. Это действие нельзя отменить." @@ -1214,6 +1222,10 @@ msgstr "Содержание" msgid "Content is laid out in a grid." msgstr "Содержимое расположено в виде сетки." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Финский" msgid "Fireworks" msgstr "Фейерверк" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Подходит для просмотра" diff --git a/apps/web/locales/sk-SK.po b/apps/web/locales/sk-SK.po index 635f4fb5c..ab715dbb3 100644 --- a/apps/web/locales/sk-SK.po +++ b/apps/web/locales/sk-SK.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" a celá časová os budú natrvalo odstránené. Túto akciu nie je možné vrátiť späť." @@ -1214,6 +1222,10 @@ msgstr "Obsah" msgid "Content is laid out in a grid." msgstr "Obsah je usporiadaný v mriežke." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Fínčina" msgid "Fireworks" msgstr "Ohňostroj" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Prispôsobiť zobrazeniu" diff --git a/apps/web/locales/sl-SI.po b/apps/web/locales/sl-SI.po index 47fcbb825..a8906b37a 100644 --- a/apps/web/locales/sl-SI.po +++ b/apps/web/locales/sl-SI.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" in celotna časovnica bosta trajno izbrisana. Tega ni mogoče razveljaviti." @@ -1214,6 +1222,10 @@ msgstr "Vsebina" msgid "Content is laid out in a grid." msgstr "Vsebina je razporejena v mreži." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finščina" msgid "Fireworks" msgstr "Ognjemet" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Prilagodi pogledu" diff --git a/apps/web/locales/sq-AL.po b/apps/web/locales/sq-AL.po index 9156f0418..3601d5458 100644 --- a/apps/web/locales/sq-AL.po +++ b/apps/web/locales/sq-AL.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" dhe e gjithë kronologjia e saj do të fshihen përgjithmonë. Kjo nuk mund të zhbëhet." @@ -1214,6 +1222,10 @@ msgstr "Përmbajtja" msgid "Content is laid out in a grid." msgstr "Përmbajtja është vendosur në një rrjetë." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finlandisht" msgid "Fireworks" msgstr "Fishekzjarre" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "I përshtatshëm për t’u parë" diff --git a/apps/web/locales/sr-SP.po b/apps/web/locales/sr-SP.po index 9009d1395..6998336b7 100644 --- a/apps/web/locales/sr-SP.po +++ b/apps/web/locales/sr-SP.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" и цела временска линија биће трајно избрисани. Ово не може да се опозове." @@ -1214,6 +1222,10 @@ msgstr "Садржај" msgid "Content is laid out in a grid." msgstr "Садржај је распоређен у мрежи." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Фински" msgid "Fireworks" msgstr "Ватромет" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Прилагоди приказу" diff --git a/apps/web/locales/sv-SE.po b/apps/web/locales/sv-SE.po index 4af8cf767..c91e82e48 100644 --- a/apps/web/locales/sv-SE.po +++ b/apps/web/locales/sv-SE.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" och hela tidslinjen tas bort permanent. Detta går inte att ångra." @@ -1214,6 +1222,10 @@ msgstr "Innehåll" msgid "Content is laid out in a grid." msgstr "Innehållet är upplagt i ett rutnät." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Finska" msgid "Fireworks" msgstr "Fyrverkeri" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Anpassa för visning" diff --git a/apps/web/locales/ta-IN.po b/apps/web/locales/ta-IN.po index b7f935091..2555c6ca4 100644 --- a/apps/web/locales/ta-IN.po +++ b/apps/web/locales/ta-IN.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" மற்றும் அதன் முழு காலவரிசை நிரந்தரமாக நீக்கப்படும். இதை மீட்டெடுக்க முடியாது." @@ -1214,6 +1222,10 @@ msgstr "உள்ளடக்கம்" msgid "Content is laid out in a grid." msgstr "உள்ளடக்கம் ஒரு கட்டமைப்பில் (கிரிட்) அமைக்கப்பட்டுள்ளது." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "ஃபின்னிஷ்" msgid "Fireworks" msgstr "பட்டாசுகள்" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "பார்வைக்கு ஏற்றது" diff --git a/apps/web/locales/te-IN.po b/apps/web/locales/te-IN.po index 798ec5467..13e3c1174 100644 --- a/apps/web/locales/te-IN.po +++ b/apps/web/locales/te-IN.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" మరియు దాని పూర్తి టైమ్‌లైన్ శాశ్వతంగా తొలగించబడతాయి. దీన్ని తిరిగి చేయలేరు." @@ -1214,6 +1222,10 @@ msgstr "విషయం" msgid "Content is laid out in a grid." msgstr "కంటెంట్ గ్రిడ్‌లో అమర్చబడింది." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "ఫిన్నిష్" msgid "Fireworks" msgstr "బాణసంచా" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "వీక్షించడానికి సరిపోతుంది" diff --git a/apps/web/locales/th-TH.po b/apps/web/locales/th-TH.po index 76a2dd2f7..d29922384 100644 --- a/apps/web/locales/th-TH.po +++ b/apps/web/locales/th-TH.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" และไทม์ไลน์ทั้งหมดจะถูกลบถาวร ไม่สามารถย้อนกลับได้" @@ -1214,6 +1222,10 @@ msgstr "เนื้อหา" msgid "Content is laid out in a grid." msgstr "เนื้อหาถูกจัดวางแบบกริด" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "ฟินแลนด์" msgid "Fireworks" msgstr "ดอกไม้ไฟ" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "เหมาะสำหรับรับชม" diff --git a/apps/web/locales/tr-TR.po b/apps/web/locales/tr-TR.po index 2ee7c5807..2d126adaf 100644 --- a/apps/web/locales/tr-TR.po +++ b/apps/web/locales/tr-TR.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" ve tüm zaman çizelgesi kalıcı olarak silinecek. Bu işlem geri alınamaz." @@ -1214,6 +1222,10 @@ msgstr "İçerik" msgid "Content is laid out in a grid." msgstr "İçerik bir ızgara düzeninde yerleştirilmiş." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Fince" msgid "Fireworks" msgstr "Havai fişekler" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Görüntülemeye uygun" diff --git a/apps/web/locales/uk-UA.po b/apps/web/locales/uk-UA.po index e6c90d80d..0cd7ed671 100644 --- a/apps/web/locales/uk-UA.po +++ b/apps/web/locales/uk-UA.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" і всю хронологію буде назавжди видалено. Це не можна скасувати." @@ -1214,6 +1222,10 @@ msgstr "Зміст" msgid "Content is laid out in a grid." msgstr "Вміст розташований у вигляді сітки." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Фінська" msgid "Fireworks" msgstr "Феєрверки" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Пристосувати до перегляду" diff --git a/apps/web/locales/uz-UZ.po b/apps/web/locales/uz-UZ.po index ccf6ec298..5b469dbdf 100644 --- a/apps/web/locales/uz-UZ.po +++ b/apps/web/locales/uz-UZ.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" va uning to‘liq xronologiyasi butunlay o‘chiriladi. Buni ortga qaytarib bo‘lmaydi." @@ -1214,6 +1222,10 @@ msgstr "Mazmun" msgid "Content is laid out in a grid." msgstr "Kontent panjara koʻrinishida joylashtirilgan." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Fin tili" msgid "Fireworks" msgstr "Mushakbozlik" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Ko'rish uchun moslash" diff --git a/apps/web/locales/vi-VN.po b/apps/web/locales/vi-VN.po index 270ff2c42..69c012d15 100644 --- a/apps/web/locales/vi-VN.po +++ b/apps/web/locales/vi-VN.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" và toàn bộ dòng thời gian sẽ bị xóa vĩnh viễn. Không thể hoàn tác." @@ -1214,6 +1222,10 @@ msgstr "Nội dung" msgid "Content is laid out in a grid." msgstr "Nội dung được trình bày theo dạng lưới." +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "Tiếng Phần Lan" msgid "Fireworks" msgstr "Pháo hoa" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "Phù hợp để xem" diff --git a/apps/web/locales/zh-CN.po b/apps/web/locales/zh-CN.po index 2b3c14f44..a79e1e336 100644 --- a/apps/web/locales/zh-CN.po +++ b/apps/web/locales/zh-CN.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" 及其完整时间线将被永久删除。此操作无法撤销。" @@ -1214,6 +1222,10 @@ msgstr "内容" msgid "Content is laid out in a grid." msgstr "内容以网格形式排列。" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "芬兰语" msgid "Fireworks" msgstr "烟花" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "适合观看" diff --git a/apps/web/locales/zh-TW.po b/apps/web/locales/zh-TW.po index 0386d8dab..2e92ddab5 100644 --- a/apps/web/locales/zh-TW.po +++ b/apps/web/locales/zh-TW.po @@ -23,6 +23,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "\"{0} · {1}\" 及其完整時間軸將被永久刪除。此操作無法復原。" @@ -1214,6 +1222,10 @@ msgstr "內容" msgid "Content is laid out in a grid." msgstr "內容以網格形式排列。" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2369,6 +2381,10 @@ msgstr "芬蘭語" msgid "Fireworks" msgstr "煙火" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "適合觀看" diff --git a/apps/web/locales/zu-ZA.po b/apps/web/locales/zu-ZA.po index fc75edde7..4d72b13d7 100644 --- a/apps/web/locales/zu-ZA.po +++ b/apps/web/locales/zu-ZA.po @@ -18,6 +18,14 @@ msgstr "" #. placeholder {1}: application.company #. placeholder {1}: current.company #: src/features/applications/components/application-actions-menu.tsx +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover" +msgstr "" + +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore edges removed by an earlier crop." +msgstr "" + #: src/features/applications/components/application-detail-sheet.tsx msgid "\"{0} · {1}\" and its full timeline will be permanently deleted. This can't be undone." msgstr "" @@ -1200,6 +1208,10 @@ msgstr "" msgid "Content is laid out in a grid." msgstr "" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Contain" +msgstr "" + #. Button label to continue to dashboard after successful registration #. Final action button after saving backup codes #. Primary action button to proceed to next step in two-factor setup @@ -2351,6 +2363,10 @@ msgstr "" msgid "Fireworks" msgstr "" +#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +msgid "Fit" +msgstr "" + #: src/routes/builder/$resumeId/-components/dock.tsx msgid "Fit to view" msgstr "" diff --git a/apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/field-labels.test.tsx b/apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/field-labels.test.tsx index eee7bab95..ed6df5fd9 100644 --- a/apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/field-labels.test.tsx +++ b/apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/field-labels.test.tsx @@ -12,7 +12,7 @@ import { defaultResumeData } from "@reactive-resume/schema/resume/default"; import { BasicsSectionBuilder } from "./basics"; import { PictureSectionBuilder } from "./picture"; -const state = vi.hoisted(() => ({ data: {} as ResumeData, update: vi.fn() })); +const state = vi.hoisted(() => ({ data: {} as ResumeData, update: vi.fn(), uploadFile: vi.fn() })); vi.mock("@/features/resume/builder/draft", () => ({ useCurrentBuilderResumeSelector: (selector: (resume: { data: ResumeData }) => unknown) => selector(state), @@ -27,7 +27,7 @@ vi.mock("@/libs/tanstack-form", async () => { vi.mock("@/libs/orpc/client", () => ({ orpc: { storage: { - uploadFile: { mutationOptions: () => ({ mutationFn: vi.fn() }) }, + uploadFile: { mutationOptions: () => ({ mutationFn: state.uploadFile }) }, deleteFile: { mutationOptions: () => ({ mutationFn: vi.fn() }) }, }, }, @@ -47,6 +47,8 @@ beforeEach(() => { state.data = structuredClone(defaultResumeData); state.update.mockReset(); state.update.mockImplementation((update: (draft: ResumeData) => void) => update(state.data)); + state.uploadFile.mockReset(); + state.uploadFile.mockResolvedValue({ url: "/uploads/picture.png" }); }); function renderSection(children: ReactNode) { @@ -83,4 +85,69 @@ describe("builder field labels", () => { fireEvent.change(input, { target: { value: "144" } }); await waitFor(() => expect(state.data.picture.size).toBe(144)); }); + + it("persists named fit choices and previews contain without cropping", async () => { + const user = userEvent.setup(); + renderSection(); + + expect(screen.getByRole("group", { name: "Fit" })).toBeInTheDocument(); + await user.click(screen.getByRole("button", { name: "Contain" })); + await waitFor(() => expect(state.data.picture.fit).toBe("contain")); + + const file = new File(["full-image"], "full.png", { type: "image/png" }); + await user.upload(screen.getAllByLabelText("Upload picture")[0] as HTMLInputElement, file); + + await waitFor(() => expect(state.uploadFile).toHaveBeenCalledOnce()); + expect(state.uploadFile.mock.calls[0]?.[0]).toBe(file); + const preview = screen.getByRole("button", { name: "Delete picture" }).querySelector("img"); + expect(preview).toBeInTheDocument(); + expect(getComputedStyle(preview as HTMLImageElement).objectFit).toBe("contain"); + expect(screen.queryByRole("dialog", { name: "Crop picture" })).not.toBeInTheDocument(); + }); + + it("keeps cover uploads in cancelable crop flow", async () => { + const user = userEvent.setup(); + renderSection(); + + const file = new File(["crop-image"], "crop.png", { type: "image/png" }); + await user.upload(screen.getAllByLabelText("Upload picture")[0] as HTMLInputElement, file); + const dialog = screen.getByRole("dialog", { name: "Crop picture" }); + expect(dialog).toBeInTheDocument(); + + await user.click(screen.getByRole("button", { name: "Cancel" })); + expect(dialog).not.toBeInTheDocument(); + expect(state.uploadFile).not.toHaveBeenCalled(); + }); + + it("retries the same full contain file after an upload error", async () => { + state.uploadFile.mockRejectedValueOnce(new Error("Upload failed")); + const user = userEvent.setup(); + renderSection(); + + await user.click(screen.getByRole("button", { name: "Contain" })); + const file = new File(["full-image"], "full.png", { type: "image/png" }); + const input = screen.getAllByLabelText("Upload picture")[0] as HTMLInputElement; + await user.upload(input, file); + + await waitFor(() => expect(state.uploadFile).toHaveBeenCalledOnce()); + expect(state.data.picture.url).toBe(""); + expect(screen.queryByRole("dialog", { name: "Crop picture" })).not.toBeInTheDocument(); + await waitFor(() => expect(input.files).toHaveLength(0)); + + await user.upload(input, file); + await waitFor(() => expect(state.uploadFile).toHaveBeenCalledTimes(2)); + expect(state.uploadFile.mock.calls[0]?.[0]).toBe(file); + expect(state.uploadFile.mock.calls[1]?.[0]).toBe(file); + }); + + it("disables fit and upload controls inside the builder lock fieldset", () => { + renderSection( +
+ +
, + ); + + expect(screen.getByRole("button", { name: "Contain" })).toBeDisabled(); + expect(screen.getAllByLabelText("Upload picture")[0]).toBeDisabled(); + }); }); diff --git a/apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx b/apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx index 52a94bdf4..453a663c5 100644 --- a/apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx +++ b/apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx @@ -94,7 +94,8 @@ function PicturePreviewControls({ )} @@ -143,12 +144,58 @@ function PicturePreviewControls({ ); } -type PictureGeometryFieldsProps = { +type PictureFieldProps = { form: PictureSettingsForm; onAutoSave: () => void; }; -function PictureGeometryFields({ form, onAutoSave }: PictureGeometryFieldsProps) { +function PictureFitField({ form, onAutoSave }: PictureFieldProps) { + return ( + + {(field) => ( +
+
+ Fit +
+ + + + +

+ + Cover crops the image to fill the frame. Contain shows the whole image. Reupload the original to restore + edges removed by an earlier crop. + +

+
+ )} +
+ ); +} + +function PictureGeometryFields({ form, onAutoSave }: PictureFieldProps) { return ( <> @@ -519,7 +566,6 @@ function PictureSectionForm() { form.setFieldValue("url", url); handleAutoSave(); toast.close(toastId); - if (fileInputRef.current) fileInputRef.current.value = ""; }, onError: (error) => { toast.add({ @@ -534,12 +580,19 @@ function PictureSectionForm() { id: toastId, }); }, + onSettled: () => { + if (fileInputRef.current) fileInputRef.current.value = ""; + }, }); }; const onUploadPicture = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; + if (form.state.values.fit === "contain") { + uploadPictureFile(file); + return; + } // Open the interactive crop step instead of uploading immediately. setCropState({ file, imageSrc: URL.createObjectURL(file) }); @@ -666,6 +719,8 @@ function PictureSectionForm() { onUploadPicture={onUploadPicture} /> + +
diff --git a/docs/guides/json-resume-schema.mdx b/docs/guides/json-resume-schema.mdx index a17e2c427..ffc00d19e 100644 --- a/docs/guides/json-resume-schema.mdx +++ b/docs/guides/json-resume-schema.mdx @@ -160,6 +160,15 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest "type": "boolean", "description": "Whether to hide the picture from the resume." }, + "fit": { + "default": "cover", + "description": "How the picture fits its frame: cover crops overflow, while contain preserves the whole image.", + "type": "string", + "enum": [ + "cover", + "contain" + ] + }, "url": { "type": "string", "description": "The URL to the picture to display on the resume. Prefer local app-served paths (for example /uploads/...) populated via upload." diff --git a/packages/import/src/reactive-resume-v4-json.broad.test.ts b/packages/import/src/reactive-resume-v4-json.broad.test.ts index 64fc155d5..151caf6f0 100644 --- a/packages/import/src/reactive-resume-v4-json.broad.test.ts +++ b/packages/import/src/reactive-resume-v4-json.broad.test.ts @@ -222,6 +222,7 @@ describe("parseReactiveResumeV4JSON — broad section mapping", () => { it("maps picture with border on", () => { expect(result.picture.url).toBe("https://example.com/pic.jpg"); expect(result.picture.hidden).toBe(false); + expect(result.picture.fit).toBe("cover"); expect(result.picture.borderWidth).toBeGreaterThan(0); }); diff --git a/packages/import/src/reactive-resume-v4-json.tsx b/packages/import/src/reactive-resume-v4-json.tsx index fcb1b8cb0..41380c8f7 100644 --- a/packages/import/src/reactive-resume-v4-json.tsx +++ b/packages/import/src/reactive-resume-v4-json.tsx @@ -248,6 +248,7 @@ export function parseReactiveResumeV4JSON(json: string): ResumeData { const transformed: ResumeData = { picture: { hidden: v4Data.basics.picture?.effects?.hidden ?? false, + fit: "cover", url: v4Data.basics.picture?.url ?? "", size: clamp(v4Data.basics.picture?.size ?? 80, 32, 512), rotation: 0, diff --git a/packages/pdf/src/templates/shared/base-template-styles.ts b/packages/pdf/src/templates/shared/base-template-styles.ts index 75aee707a..f394e8759 100644 --- a/packages/pdf/src/templates/shared/base-template-styles.ts +++ b/packages/pdf/src/templates/shared/base-template-styles.ts @@ -135,7 +135,7 @@ export function createBaseTemplateStyles({ picture: { width: picture.size, height: picture.size, - objectFit: "cover", + objectFit: picture.fit, aspectRatio: picture.aspectRatio, borderRadius: picture.borderRadius, borderColor: rgbaStringToHex(picture.borderColor), diff --git a/packages/pdf/src/templates/shared/picture-fit.integration.test.tsx b/packages/pdf/src/templates/shared/picture-fit.integration.test.tsx new file mode 100644 index 000000000..97d5b2a22 --- /dev/null +++ b/packages/pdf/src/templates/shared/picture-fit.integration.test.tsx @@ -0,0 +1,254 @@ +import { describe, expect, it } from "vitest"; +import { createCanvas } from "@napi-rs/canvas"; +import { renderToBuffer } from "@react-pdf/renderer"; +import { act, createElement } from "react"; +import { defaultResumeData } from "@reactive-resume/schema/resume/default"; +import { ResumeDocument } from "../../document"; +import { rasterizePdf } from "../../semantic/test/rasterize-pdf"; + +type Fit = "cover" | "contain"; +type Orientation = "square" | "landscape" | "portrait"; + +const dimensions = { + square: [300, 300], + landscape: [400, 300], + portrait: [300, 400], +} as const; + +const marker = { + left: [255, 0, 0], + right: [0, 255, 0], + top: [0, 0, 255], + bottom: [255, 255, 0], +} as const; + +const cropMarker = { + start: [255, 128, 0], + end: [0, 128, 128], +} as const; + +const frameColor = [255, 0, 255] as const; +const pictureBounds = { left: 21, right: 170, top: 18, bottom: 167 } as const; + +function markedImage(orientation: Orientation) { + const [width, height] = dimensions[orientation]; + const canvas = createCanvas(width, height); + const context = canvas.getContext("2d"); + context.fillStyle = "#ffffff"; + context.fillRect(0, 0, width, height); + const strip = Math.round(Math.min(width, height) * 0.08); + context.fillStyle = "#ff0000"; + context.fillRect(0, 0, strip, height); + context.fillStyle = "#00ff00"; + context.fillRect(width - strip, 0, strip, height); + context.fillStyle = "#0000ff"; + context.fillRect(strip, 0, width - strip * 2, strip); + context.fillStyle = "#ffff00"; + context.fillRect(strip, height - strip, width - strip * 2, strip); + if (orientation === "landscape") { + context.fillStyle = "#ff8000"; + context.fillRect(50, 0, 5, height); + context.fillStyle = "#008080"; + context.fillRect(width - 55, 0, 5, height); + } + if (orientation === "portrait") { + context.fillStyle = "#ff8000"; + context.fillRect(0, 50, width, 5); + context.fillStyle = "#008080"; + context.fillRect(0, height - 55, width, 5); + } + context.fillStyle = "#000000"; + context.fillRect(Math.floor(width / 2) - 3, strip, 6, height - strip * 2); + context.fillRect(strip, Math.floor(height / 2) - 3, width - strip * 2, 6); + return canvas.toDataURL("image/png"); +} + +async function rasterPicture( + orientation: Orientation, + fit: Fit, + stylesheet = "@version 1;", + frame = { borderWidth: 6, shadowWidth: 8 }, +) { + const data = structuredClone(defaultResumeData); + data.basics.name = "Picture fit"; + data.metadata.typography.body.fontFamily = "Helvetica"; + data.metadata.typography.heading.fontFamily = "Helvetica"; + data.metadata.layout.pages = [{ fullWidth: false, main: [], sidebar: [] }]; + data.metadata.stylesheet = { mode: "semantic", source: { languageVersion: 1, text: stylesheet } }; + Object.assign(data.picture, { + url: markedImage(orientation), + hidden: false, + fit, + size: 100, + aspectRatio: 1, + borderRadius: 0, + borderColor: "rgba(255, 0, 255, 1)", + borderWidth: frame.borderWidth, + shadowColor: "rgba(0, 255, 255, 1)", + shadowWidth: frame.shadowWidth, + }); + const element = createElement(ResumeDocument, { data, template: "onyx" }) as unknown as Parameters< + typeof renderToBuffer + >[0]; + let bytes = new Uint8Array(); + await act(async () => { + bytes = new Uint8Array(await renderToBuffer(element)); + }); + const [page] = await rasterizePdf(bytes); + if (!page) throw new Error("Missing rendered page"); + return page; +} + +function colorBounds(page: Awaited>, color: readonly number[]) { + const points: { x: number; y: number }[] = []; + for (let index = 0; index < page.data.length; index += 4) { + if (color.every((channel, offset) => Math.abs((page.data[index + offset] ?? -255) - channel) <= 2)) { + points.push({ x: (index / 4) % page.width, y: Math.floor(index / 4 / page.width) }); + } + } + if (points.length === 0) return undefined; + return { + left: Math.min(...points.map(({ x }) => x)), + right: Math.max(...points.map(({ x }) => x)), + top: Math.min(...points.map(({ y }) => y)), + bottom: Math.max(...points.map(({ y }) => y)), + }; +} + +type Bounds = NonNullable>; + +function requiredColorBounds( + page: Awaited>, + color: readonly number[], + label: string, +): Bounds { + const bounds = colorBounds(page, color); + expect(bounds, label).toBeDefined(); + if (!bounds) throw new Error(`Missing ${label}`); + return bounds; +} + +function mergedBounds(bounds: readonly Bounds[]): Bounds { + return { + left: Math.min(...bounds.map(({ left }) => left)), + right: Math.max(...bounds.map(({ right }) => right)), + top: Math.min(...bounds.map(({ top }) => top)), + bottom: Math.max(...bounds.map(({ bottom }) => bottom)), + }; +} + +function expectBoundsWithinPixel(actual: Bounds, expected: Bounds) { + for (const edge of ["left", "right", "top", "bottom"] as const) { + expect(Math.abs(actual[edge] - expected[edge]), edge).toBeLessThanOrEqual(1); + } +} + +function boundsCenter(bounds: Bounds) { + return { + x: (bounds.left + bounds.right) / 2, + y: (bounds.top + bounds.bottom) / 2, + }; +} + +function expectSameCenter(actual: Bounds, expected: Bounds) { + const actualCenter = boundsCenter(actual); + const expectedCenter = boundsCenter(expected); + expect(Math.abs(actualCenter.x - expectedCenter.x), "center x").toBeLessThanOrEqual(1); + expect(Math.abs(actualCenter.y - expectedCenter.y), "center y").toBeLessThanOrEqual(1); +} + +function expectedContentBounds(borderWidth: number): Bounds { + const inset = borderWidth * 1.5; + return { + left: pictureBounds.left + inset, + right: pictureBounds.right - inset, + top: pictureBounds.top + inset, + bottom: pictureBounds.bottom - inset, + }; +} + +function expectedContainBounds(orientation: Orientation, borderWidth: number): Bounds { + const content = expectedContentBounds(borderWidth); + const contentWidth = content.right - content.left + 1; + const contentHeight = content.bottom - content.top + 1; + const [sourceWidth, sourceHeight] = dimensions[orientation]; + const scale = Math.min(contentWidth / sourceWidth, contentHeight / sourceHeight); + const width = sourceWidth * scale; + const height = sourceHeight * scale; + const center = boundsCenter(content); + return { + left: center.x - (width - 1) / 2, + right: center.x + (width - 1) / 2, + top: center.y - (height - 1) / 2, + bottom: center.y + (height - 1) / 2, + }; +} + +describe("picture fit geometry (#2782)", () => { + it.each(["square", "landscape", "portrait"] as const)( + "keeps every %s source edge visible and centered in contain mode", + async (orientation) => { + const page = await rasterPicture(orientation, "contain"); + const edges = Object.entries(marker).map(([name, color]) => requiredColorBounds(page, color, name)); + const image = mergedBounds(edges); + expectBoundsWithinPixel(image, expectedContainBounds(orientation, 6)); + expectSameCenter(image, requiredColorBounds(page, frameColor, "frame")); + }, + ); + + it.each([ + { orientation: "landscape", retained: ["top", "bottom"], cropped: ["left", "right"] }, + { orientation: "portrait", retained: ["left", "right"], cropped: ["top", "bottom"] }, + ] as const)( + "preserves centered cover crop geometry for $orientation sources", + async ({ orientation, retained, cropped }) => { + const page = await rasterPicture(orientation, "cover"); + for (const edge of retained) expect(colorBounds(page, marker[edge]), `${edge} retained`).toBeDefined(); + for (const edge of cropped) expect(colorBounds(page, marker[edge]), `${edge} cropped`).toBeUndefined(); + + const content = expectedContentBounds(6); + const start = requiredColorBounds(page, cropMarker.start, "start crop marker"); + const end = requiredColorBounds(page, cropMarker.end, "end crop marker"); + const visibleImage = mergedBounds([ + ...retained.map((edge) => requiredColorBounds(page, marker[edge], edge)), + start, + end, + ]); + expectBoundsWithinPixel(visibleImage, content); + expectSameCenter(mergedBounds([start, end]), content); + + if (orientation === "landscape") { + expect(Math.abs(start.left - content.left)).toBeLessThanOrEqual(1); + expect(Math.abs(end.right - content.right)).toBeLessThanOrEqual(1); + } else { + expect(Math.abs(start.top - content.top)).toBeLessThanOrEqual(1); + expect(Math.abs(end.bottom - content.bottom)).toBeLessThanOrEqual(1); + } + }, + ); + + it("keeps square cover source edges uncropped", async () => { + const page = await rasterPicture("square", "cover"); + const image = mergedBounds(Object.entries(marker).map(([name, color]) => requiredColorBounds(page, color, name))); + expectBoundsWithinPixel(image, expectedContentBounds(6)); + expectSameCenter(image, requiredColorBounds(page, frameColor, "frame")); + }); + + it.each([ + { borderWidth: 0, shadowWidth: 0 }, + { borderWidth: 6, shadowWidth: 0 }, + { borderWidth: 0, shadowWidth: 8 }, + { borderWidth: 6, shadowWidth: 8 }, + ])("retains every landscape edge with border $borderWidth and shadow $shadowWidth", async (frame) => { + const page = await rasterPicture("landscape", "contain", "@version 1;", frame); + const image = mergedBounds(Object.entries(marker).map(([name, color]) => requiredColorBounds(page, color, name))); + expectBoundsWithinPixel(image, expectedContainBounds("landscape", frame.borderWidth)); + expectSameCenter(image, expectedContentBounds(frame.borderWidth)); + }); + + it("lets semantic object-fit cover override selected contain", async () => { + const cover = await rasterPicture("landscape", "cover"); + const overridden = await rasterPicture("landscape", "contain", "@version 1; picture { object-fit: cover; }"); + expect([...overridden.data]).toEqual([...cover.data]); + }); +}); diff --git a/packages/pdf/src/templates/shared/picture.test.ts b/packages/pdf/src/templates/shared/picture.test.ts index def533ff7..7b0487c45 100644 --- a/packages/pdf/src/templates/shared/picture.test.ts +++ b/packages/pdf/src/templates/shared/picture.test.ts @@ -3,6 +3,7 @@ import { hasTemplatePicture } from "./picture"; const basePicture = { hidden: false, + fit: "cover", url: "", size: 80, rotation: 0, diff --git a/packages/schema/src/resume/data.test.ts b/packages/schema/src/resume/data.test.ts index 0a5956a25..f7a34f9de 100644 --- a/packages/schema/src/resume/data.test.ts +++ b/packages/schema/src/resume/data.test.ts @@ -241,6 +241,20 @@ describe("pictureSchema", () => { expect(pictureSchema.safeParse(defaultResumeData.picture).success).toBe(true); }); + it("defaults missing and invalid legacy fit values to cover", () => { + const { fit: _fit, ...legacyPicture } = defaultResumeData.picture; + + expect(pictureSchema.parse(legacyPicture).fit).toBe("cover"); + expect(pictureSchema.parse({ ...legacyPicture, fit: "stretch" }).fit).toBe("cover"); + }); + + it("preserves contain through parsing and JSON round-trip", () => { + const picture = pictureSchema.parse({ ...defaultResumeData.picture, fit: "contain" }); + const roundTripped = pictureSchema.parse(JSON.parse(JSON.stringify(picture))); + + expect(roundTripped.fit).toBe("contain"); + }); + it("rejects size below 32", () => { const invalid = { ...defaultResumeData.picture, size: 16 }; expect(pictureSchema.safeParse(invalid).success).toBe(false); diff --git a/packages/schema/src/resume/data.ts b/packages/schema/src/resume/data.ts index 1264596e3..d637d63fd 100644 --- a/packages/schema/src/resume/data.ts +++ b/packages/schema/src/resume/data.ts @@ -33,6 +33,10 @@ const itemWebsiteSchema = websiteSchema export const pictureSchema = z.object({ hidden: z.boolean().describe("Whether to hide the picture from the resume."), + fit: z + .enum(["cover", "contain"]) + .catch("cover") + .describe("How the picture fits its frame: cover crops overflow, while contain preserves the whole image."), url: z .string() .describe( diff --git a/packages/schema/src/resume/default.test.ts b/packages/schema/src/resume/default.test.ts index f3fd4f03a..7fa89344d 100644 --- a/packages/schema/src/resume/default.test.ts +++ b/packages/schema/src/resume/default.test.ts @@ -20,6 +20,10 @@ describe("defaultResumeData", () => { expect(defaultResumeData.metadata.page.format).toBe("a4"); }); + it("uses cover picture fitting by default", () => { + expect(defaultResumeData.picture.fit).toBe("cover"); + }); + it("shows link underlines by default", () => { expect(defaultResumeData.metadata.page.hideLinkUnderline).toBe(false); }); diff --git a/packages/schema/src/resume/default.ts b/packages/schema/src/resume/default.ts index 12798a2c7..3126d8fc6 100644 --- a/packages/schema/src/resume/default.ts +++ b/packages/schema/src/resume/default.ts @@ -14,6 +14,7 @@ const section = (icon: string) => ({ export const defaultResumeData: ResumeData = { picture: { hidden: false, + fit: "cover", url: "", size: 80, rotation: 0, diff --git a/packages/schema/src/resume/json-schema.test.ts b/packages/schema/src/resume/json-schema.test.ts index d00f76a28..ab8184e97 100644 --- a/packages/schema/src/resume/json-schema.test.ts +++ b/packages/schema/src/resume/json-schema.test.ts @@ -21,6 +21,16 @@ describe("createResumeDataJsonSchema", () => { }); }); + it("accepts legacy input with omitted picture fit", () => { + const schema = createResumeDataJsonSchema(); + + expect(schema).toMatchObject({ + properties: { + picture: { required: expect.not.arrayContaining(["fit"]) }, + }, + }); + }); + it("enforces the custom-section type and item correlation", () => { const generatedSchema = z.fromJSONSchema(createResumeDataJsonSchema()); const valid = { diff --git a/packages/schema/src/resume/json-schema.ts b/packages/schema/src/resume/json-schema.ts index 9ef5aedbd..1d4bc2e15 100644 --- a/packages/schema/src/resume/json-schema.ts +++ b/packages/schema/src/resume/json-schema.ts @@ -9,7 +9,13 @@ const toInputJsonSchema = (schema: z.ZodType) => }); export function createResumeDataJsonSchema() { - return toInputJsonSchema(resumeDataSchema); + const schema = toInputJsonSchema(resumeDataSchema); + const picture = schema.properties?.picture; + // Zod's catch accepts an omitted fit at runtime but still marks the property required in JSON Schema. + if (picture && typeof picture !== "boolean" && Array.isArray(picture.required)) { + picture.required = picture.required.filter((property) => property !== "fit"); + } + return schema; } export function createCustomSectionItemJsonSchemas() { diff --git a/packages/schema/src/resume/sample.ts b/packages/schema/src/resume/sample.ts index df1510e1c..e85a55985 100644 --- a/packages/schema/src/resume/sample.ts +++ b/packages/schema/src/resume/sample.ts @@ -3,6 +3,7 @@ import type { ResumeData } from "./data"; export const sampleResumeData: ResumeData = { picture: { hidden: false, + fit: "cover", url: "/photos/sample-picture.jpg", size: 100, rotation: 0, diff --git a/skills/resume-builder/references/schema.md b/skills/resume-builder/references/schema.md index 641b256be..eff95d707 100644 --- a/skills/resume-builder/references/schema.md +++ b/skills/resume-builder/references/schema.md @@ -35,6 +35,7 @@ Choose one coherent shape for each union value. Required fields are local to tha | --- | --- | --- | --- | --- | | `picture` | `object` | yes | — | Configuration for photograph displayed on the resume | | `picture.hidden` | `boolean` | yes | — | Whether to hide the picture from the resume. | +| `picture.fit` | `string` | no | enum: ["cover","contain"]; default: "cover" | How the picture fits its frame: cover crops overflow, while contain preserves the whole image. | | `picture.url` | `string` | yes | — | The URL to the picture to display on the resume. Prefer local app-served paths (for example /uploads/...) populated via upload. | | `picture.size` | `number` | yes | minimum: 32; maximum: 512 | The size of the picture to display on the resume, defined in points (pt). | | `picture.rotation` | `number` | yes | minimum: 0; maximum: 360 | The rotation of the picture to display on the resume, defined in degrees (°). | diff --git a/tests/e2e/specs/picture-rendering.spec.ts b/tests/e2e/specs/picture-rendering.spec.ts new file mode 100644 index 000000000..a0f4fb17f --- /dev/null +++ b/tests/e2e/specs/picture-rendering.spec.ts @@ -0,0 +1,237 @@ +import type { Page, TestInfo } from "@playwright/test"; +import { readFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { Pool } from "pg"; +import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume"; +import { expect, test } from "../fixtures/test"; + +type MarkerBounds = { + left: number; + right: number; + top: number; + bottom: number; + count: number; +}; + +type MarkerMetrics = Record<"red" | "green" | "blue" | "yellow", MarkerBounds | null>; + +const requireRoot = createRequire(`${process.cwd()}/package.json`); + +async function installPdfJsRoute(page: Page) { + await page.route("**/__picture_pdfjs/*", async (route) => { + const worker = new URL(route.request().url()).pathname.endsWith("worker.mjs"); + await route.fulfill({ + contentType: "text/javascript", + path: requireRoot.resolve(`pdfjs-dist/legacy/build/${worker ? "pdf.worker.mjs" : "pdf.mjs"}`), + }); + }); +} + +function markedLandscapePng(dataUrl: string) { + return Buffer.from(dataUrl.slice(dataUrl.indexOf(",") + 1), "base64"); +} + +function createMarkedLandscape(page: Page) { + return page.evaluate(() => { + const canvas = document.createElement("canvas"); + canvas.width = 800; + canvas.height = 600; + const context = canvas.getContext("2d"); + if (!context) throw new Error("Canvas is unavailable"); + context.fillStyle = "#ffffff"; + context.fillRect(0, 0, 800, 600); + context.fillStyle = "#ff0000"; + context.fillRect(0, 0, 64, 600); + context.fillStyle = "#00ff00"; + context.fillRect(736, 0, 64, 600); + context.fillStyle = "#0000ff"; + context.fillRect(64, 0, 672, 48); + context.fillStyle = "#ffff00"; + context.fillRect(64, 552, 672, 48); + context.fillStyle = "#000000"; + context.fillRect(394, 48, 12, 504); + context.fillRect(64, 294, 672, 12); + return canvas.toDataURL("image/png"); + }); +} + +function markerMetricsFromPdf(page: Page, bytes: Uint8Array): Promise { + return page.evaluate(async (pdfBytes) => { + const pdfjs: typeof import("pdfjs-dist/legacy/build/pdf.mjs") = await import( + `${location.origin}/__picture_pdfjs/pdf.mjs` + ); + pdfjs.GlobalWorkerOptions.workerSrc = `${location.origin}/__picture_pdfjs/worker.mjs`; + const task = pdfjs.getDocument({ data: Uint8Array.from(pdfBytes) }); + try { + const pdfPage = await task.promise.then((pdf) => pdf.getPage(1)); + const viewport = pdfPage.getViewport({ scale: 1.5 }); + const canvas = document.createElement("canvas"); + canvas.width = Math.ceil(viewport.width); + canvas.height = Math.ceil(viewport.height); + const context = canvas.getContext("2d"); + if (!context) throw new Error("Canvas is unavailable"); + await pdfPage.render({ canvas, canvasContext: context, viewport, background: "white" }).promise; + const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data; + const predicates = { + red: (r: number, g: number, b: number) => r > 220 && g < 50 && b < 50, + green: (r: number, g: number, b: number) => r < 50 && g > 220 && b < 50, + blue: (r: number, g: number, b: number) => r < 50 && g < 50 && b > 220, + yellow: (r: number, g: number, b: number) => r > 220 && g > 220 && b < 50, + }; + const result = {} as Record; + for (const [name, predicate] of Object.entries(predicates) as Array< + [keyof typeof predicates, (r: number, g: number, b: number) => boolean] + >) { + let left = Number.POSITIVE_INFINITY; + let right = Number.NEGATIVE_INFINITY; + let top = Number.POSITIVE_INFINITY; + let bottom = Number.NEGATIVE_INFINITY; + let count = 0; + for (let index = 0; index < pixels.length; index += 4) { + if (!predicate(pixels[index] ?? 0, pixels[index + 1] ?? 0, pixels[index + 2] ?? 0)) continue; + const x = (index / 4) % canvas.width; + const y = Math.floor(index / 4 / canvas.width); + left = Math.min(left, x); + right = Math.max(right, x); + top = Math.min(top, y); + bottom = Math.max(bottom, y); + count++; + } + result[name] = count > 0 ? { left, right, top, bottom, count } : null; + } + return result; + } finally { + await task.destroy(); + } + }, Array.from(bytes)); +} + +async function exportBuilderFile(page: Page, testInfo: TestInfo, buttonName: "Download JSON" | "Download PDF") { + await openSidebarSection(page, "Export"); + await page.getByRole("button", { name: /Choose PDF, DOCX, Markdown, or JSON/ }).click(); + const pending = page.waitForEvent("download"); + await page.getByRole("button", { name: buttonName, exact: true }).click(); + const download = await pending; + const path = testInfo.outputPath(download.suggestedFilename()); + await download.saveAs(path); + await page.keyboard.press("Escape"); + return path; +} + +test("uploads and persists full Contain image with browser/server PDF parity", async ({ + authPage: page, + account, +}, info) => { + test.setTimeout(120_000); + await page.setViewportSize({ width: 1920, height: 950 }); + await installPdfJsRoute(page); + await createSampleResumeFromDashboard(page, info); + const resumeId = page.url().split("/").at(-1); + if (!resumeId) throw new Error("Missing resume id"); + + await openSidebarSection(page, "Picture"); + await page.getByRole("button", { name: "Contain", exact: true }).click(); + await expect(page.getByRole("button", { name: "Contain", exact: true })).toHaveAttribute("aria-pressed", "true"); + await page.getByRole("button", { name: "Undo", exact: true }).click(); + await expect(page.getByRole("button", { name: "Cover", exact: true })).toHaveAttribute("aria-pressed", "true"); + await page.getByRole("button", { name: "Redo", exact: true }).click(); + await expect(page.getByRole("button", { name: "Contain", exact: true })).toHaveAttribute("aria-pressed", "true"); + const dataUrl = await createMarkedLandscape(page); + const inputBytes = markedLandscapePng(dataUrl); + await info.attach("marked-landscape.png", { body: inputBytes, contentType: "image/png" }); + await page.locator('#sidebar-picture input[type="file"]').setInputFiles({ + name: "marked-landscape.png", + mimeType: "image/png", + buffer: inputBytes, + }); + await expect(page.getByRole("dialog", { name: "Crop picture" })).toHaveCount(0); + const pictureUrl = page.locator("#sidebar-picture input[name=url]"); + await expect(pictureUrl).toHaveValue(/\/uploads\//); + const sidebarPreview = page.getByRole("button", { name: "Delete picture" }).locator("img"); + await expect(sidebarPreview).toBeVisible(); + await expect(sidebarPreview).toHaveCSS("object-fit", "contain"); + const storedUrl = await pictureUrl.inputValue(); + const storedRequestUrl = storedUrl.startsWith("/uploads/") ? `/api${storedUrl}` : storedUrl; + const storedResponse = await page.request.get(storedRequestUrl); + expect(storedResponse.ok()).toBe(true); + expect(storedResponse.headers()["content-type"]).toMatch(/^image\//); + const storedBytes = await storedResponse.body(); + await info.attach("stored-landscape", { body: storedBytes, contentType: storedResponse.headers()["content-type"] }); + const storedGeometry = await page.evaluate(async (url) => { + const image = new Image(); + image.src = url; + await image.decode(); + const canvas = document.createElement("canvas"); + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + const context = canvas.getContext("2d"); + if (!context) throw new Error("Canvas is unavailable"); + context.drawImage(image, 0, 0); + const sample = (x: number, y: number) => [...context.getImageData(x, y, 1, 1).data.slice(0, 3)]; + return { + width: image.naturalWidth, + height: image.naturalHeight, + left: sample(Math.floor(image.naturalWidth * 0.02), Math.floor(image.naturalHeight / 2)), + right: sample(Math.floor(image.naturalWidth * 0.98), Math.floor(image.naturalHeight / 2)), + top: sample(Math.floor(image.naturalWidth / 4), Math.floor(image.naturalHeight * 0.02)), + bottom: sample(Math.floor(image.naturalWidth / 4), Math.floor(image.naturalHeight * 0.98)), + }; + }, storedRequestUrl); + expect(storedGeometry).toMatchObject({ width: 800, height: 600 }); + expect(storedGeometry.left[0]).toBeGreaterThan(220); + expect(storedGeometry.left[1]).toBeLessThan(50); + expect(storedGeometry.right[0]).toBeLessThan(50); + expect(storedGeometry.right[1]).toBeGreaterThan(220); + expect(storedGeometry.top[2]).toBeGreaterThan(220); + expect(storedGeometry.bottom[0]).toBeGreaterThan(220); + expect(storedGeometry.bottom[1]).toBeGreaterThan(220); + + const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + let slug = ""; + try { + await expect + .poll(async () => { + const result = await pool.query<{ data: { picture: { fit?: string; url?: string } }; slug: string }>( + "select data, slug from resume where id = $1", + [resumeId], + ); + slug = result.rows[0]?.slug ?? ""; + return result.rows[0]?.data.picture; + }) + .toMatchObject({ fit: "contain", url: storedUrl }); + + await page.getByRole("button", { name: "Cover", exact: true }).click(); + await page.getByRole("button", { name: "Contain", exact: true }).click(); + expect(await pictureUrl.inputValue()).toBe(storedUrl); + + await page.reload(); + await openSidebarSection(page, "Picture"); + await expect(page.getByRole("button", { name: "Contain", exact: true })).toHaveAttribute("aria-pressed", "true"); + await expect(page.locator("#sidebar-picture input[name=url]")).toHaveValue(storedUrl); + + await pool.query("update resume set is_public = true, updated_at = now() where id = $1", [resumeId]); + } finally { + await pool.end(); + } + + const jsonPath = await exportBuilderFile(page, info, "Download JSON"); + const exported = JSON.parse(await readFile(jsonPath, "utf8")) as { picture: { fit?: string; url?: string } }; + expect(exported.picture).toMatchObject({ fit: "contain", url: storedUrl }); + + const browserPdfPath = await exportBuilderFile(page, info, "Download PDF"); + const browserPdf = new Uint8Array(await readFile(browserPdfPath)); + const serverResponse = await page.request.get(`/api/resumes/${account.username}/${slug}/pdf`); + expect(serverResponse.ok()).toBe(true); + const serverPdf = new Uint8Array(await serverResponse.body()); + const browserMarkers = await markerMetricsFromPdf(page, browserPdf); + const serverMarkers = await markerMetricsFromPdf(page, serverPdf); + for (const name of ["red", "green", "blue", "yellow"] as const) { + expect(browserMarkers[name]?.count).toBeGreaterThan(100); + expect(serverMarkers[name]?.count).toBeGreaterThan(100); + expect(serverMarkers[name]).toEqual(browserMarkers[name]); + } + await info.attach("pdf-marker-metrics", { + body: JSON.stringify({ browserMarkers, serverMarkers }), + contentType: "application/json", + }); +});